Categoría: java (Página 1 de 2)

Manejo de Excepciones en Java

El manejo de excepciones es un punto muy importante de cualquier programa, en esta página se explica todo lo concerniente a Manejo de Excepciones en Java, sintaxis y principales usos.

Si quieres saber más sobre la historia y funcionamiento de Java visita este link.

1. Generalidades del Manejo de Excepciones en Java

En general un programa puede fallar por diversas razones, por ejemplo un error de código, un recurso no disponible o un valor de una entrada incorrecto. Y aunque existen muchas otras razones para que se produzcan fallos, es muy importante al momento de programar tener en cuenta estos posibles errores y tratarlos.

Una excepción es la forma de decir que algo no funcionó como se esperaba, aquí es bueno considerar que la forma tradicional de manejar estos casos ha sido con códigos de error, sin embargo un programa profesional debería tener un manejo robusto de errores, lo cual implica en Java, la definición de diferentes clases que puedan representar errores y el manejo apropiado de los mismos.

2. Árbol de Excepciones, Excepciones verificadas y no verificadas

En Java no tenemos que empezar desde cero a escribir las excepciones, ya existen una gran cantidad de clases que las pueden representar. En la figura 1, se muestran la jerarquía de estas excepciones.

ExcepcionesJava
Figura 1. Excepciones en Java

Esta imagen muestra las principales clases para manejar excepciones todas ellas pertenecientes al paquete java.lang.

Las excepciones Error y RuntimeException y las que derivan de ellas vía herencia, se llaman excepciones no verificadas, con lo cual cuando un método lanza estas excepciones no es necesario realizar el proceso de captura como se explicará más adelante. En el caso de las excepciones no verificadas estas aparecerán en tiempo de ejecución causando la terminación del programa de forma abrupta sino fueron debidamente capturadas.

Las Excepciones referidas como Excpetion y derivadas de esta clase deben ser capturadas de forma obligatoria o el compilador arrojará un mensaje de error.

Como nota adicional, las excepciones siempre terminan en la palabra Exception, esto es más una convención que algo obligatorio, pero es bueno seguirla pues es la costumbre entre los desarrolladores de Java.

3. Lanzando y capturando Excepciones

Para lanzar una excepción basta que un método propio del entorno de Java o de una librería lo lance o también se puede realizar un lanzamiento manual a través de la palabra reservada throw, que requerirá una instancia de la clase de Excepción que se va a lanzar.

throw new Exception("Error detail");

Para capturar una Excepción basta con ejecutar el código que puede lanzar una excepción usando la estructura de control try – catch, que en general se muestra en la siguiente fragmento de código.

try {
    // Código susceptible de error
} catch(TipoExcepcion_1 ex1) {
    // Procesamiento del error
} catch(TipoExcepcion_2 ex2) {
    // Procesamiento del error
} catch(TipoExcepcion_n exn) {
    // Procesamiento del error
} finally {
    // Procesamiento obligatorio
}

En la sección try, se empezará a ejecutar el código, tan pronto se lance una excepción se dejará de ejecutar código y se continuará con la verificación de las cláusulas catch.

Los bloques catch atraparan la primera excepción que coincida con el error lanzado en la sección try. El orden de los catch es importante porque solo se procesará el primero. Además es conveniente indicar que la sección finally siempre se ejecutará, esta normalmente es reservada para liberar recursos.

Es frecuente encontrar que en los bloques catch o finally se vuelvan a lanzar excepciones en caso que el bloque o el punto donde se encuentra el código no sea capaz de manejarlo, a esto se le conoce como lanzamiento de segundas excepciones.

4. Manejo de Excepciones en los métodos

Cuando creamos un método en Java es posible que el mismo no tenga la capacidad o la suficiente información como para manejar alguna excepción en este caso es posible indicar en la firma del método, las excepciones que no se manejan allí y que puede eventualmente lanzar. En estos casos, la parte del código que llama a estos métodos deberá capturarlos con estructuras try – catch o a su vez volverlos a indicar que no puede manejar las excepciones. Estos e puede repetir, método tras método hasta que alguno de ellos maneje la excepción o hasta que se llegue al punto de entrada del programa, es decir al main.

ReturnType name(parameters) throws ClassException1, ClassException2 {
}

Note lo siguiente:

  • Al método main se le puede agregar la clausula throws
  • No es lo mismo throw para lanzar excepciones, que throws para indicar que un método puede lanzar esas Excepciones.
  • La cláusula throws puede tener varias clases de Excepción separadas por coma.
  • Las cláusulas throws pueden servir para diferenciar métodos con los mismos nombres, por lo cual, estas excepciones sirven para sobrecargar los métodos.

5. Excepciones personalizadas

El programador puede crear sus propias clases de Excepción para representar situaciones que deban ser controladas según el dominio del software en cuestión. Para hacer esto basta con heredar de alguno de las clases típicas de Excepción, mostrada en la Figura 1. Normalmente será heredado de Exception.

Adicionalmente, es común que se provea un mensaje explicativo de la Excepción redefiniendo el contenido del atributo message en la clase Exception.

6. Try – Catch con recursos

En programación es muy habitual que se manejen recursos, como archivos, páginas web y conexiones a bases de datos, todos estos siguen una regla general y es que cualquier recurso que se abra debe cerrarse, en caso de no hacerlo se corren grandes riegos de problemas debido agotar recursos, concurrencia y en general efectos indeseados difíciles de detectar.

Debido a lo habitual que es que los programadores olviden cerrar los recursos existe una variante de la estructura try – catch llamada try – catch with resources, que cierra automáticamente estos recursos por el programador.

La forma de definirlo es muy similar simplemente se debe declarar aquellos recursos que se puedan cerrar en los paréntesis del try tal como se muestra en el código 4.

try (FileReader fr = new FileReader(path);
    BufferedReader br = new BufferedReader(fr)) {
    return br.readLine();
}

Para que los recursos puedan ser utilizados entre los paréntesis deben haber implementado la interface AutoCloseable o Closeable y al final de del try se ejecutaran los métodos close de los respectivos recursos.

7. Artículos de Interés

Listas con Arreglos en Java

Las Listas con Arreglos en Java permiten que estas estructuras de datos se desarrollen usando memoria estática, con lo cual se requerirá operaciones de redimensionado de los arreglos cada vez que se cambie el tamaño, este post detalla el funcionamiento y el código de esta estructura de datos.

Acceda al código de las listas con memoria dinámica o enlazadas usando este link.

1. Lista con arreglos

Como se había mencionado anteriormente la lista con arreglos puede ser una implementación de la misma interface IList.

La ventaja en este caso es la facilidad para posicionarse en cualquier punto, sin embargo, al estar la lista conformada con arreglos, tiene las desventajas del uso de arreglos que se menciono al principio de esta sección.

El arreglo es muy óptimo para indexare, pero cuando se acaba el tamaño es necesario crear otro nuevo con más tamaño y hacer un copiado de los elementos del arreglo original en el nuevo con mayor capacidad, esta operación ralentiza los métodos de la lista.

Por esta razón las listas con arreglos deben tener mínimamente dos conceptos incluidos: el tamaño (que comúnmente se refiere al tamaño ocupado) y la capacidad (que es la máxima cantidad de elementos que puede tener el arreglo utilizado en la lista). A continuación, se presenta el código de la lista enlazada usando arreglos.

package lists;
import java.util.Iterator;
/**
 * Lista que usa arreglos nativos para implementar las operaciones de una lista
 * @author ochoscar
 * @param < T > Tipo genérico que contra los item de la lista
 */
public class ArrayList< T > implements IList< T >, Iterable< T >  {
    ////////////////////
    // Atributos
    ////////////////////
    /** Arreglo nativo de la lista que contiene los elementos y su longitud es
     la capacidad máxima de elementos a almacenar*/
    private T array[];
    /** Tamaño de la lista que refleja la cantidad de casillas del arreglo usadas */
    private int listSize;
    ////////////////////
    // Métodos
    ////////////////////
    /**
     * Constructor por defecto que crea la lista con 10 casillas de capacidad
     */
    public ArrayList() {
        array = (T[]) new Object[10];
        listSize = 0;
    }
    /**
     * Constructor especificando la capacidad inicial
     * @param initCapacity Capacidad inicial
     */
    public ArrayList(int initCapacity) {
        array = (T[]) new Object[initCapacity];
        listSize = 0;
    }
    /**
     * Método que verifica si la lista esta vacía
     * @return Devuelve true si la lista esta
     * vacía y false en caso contrario
     */
    @Override
    public boolean isEmpty() {
        return listSize == 0;
    }
    /**
     * Obtiene el primer item de la lista
     * @return Devuelve la Clase al principio de la lista
     * null en caso que la lista este vacía
     */
    @Override
    public T getFirst() {
        return listSize > 0 ? array[0] : null;
    }
    /**
     * Obtiene el ultimo elemento de la lista
     * @return Devuelve la ultima Clase en la lista
     * null si la lista esta vacía
     */
    @Override
    public T getLast() {
        return listSize > 0 ? array[listSize - 1] : null;
    }
    /**
     * Devuelve el i - esimo elemento de la lista
     * @param i Posición del elemento a devolver (comienza en 0)
     * @return Devuelve la Clase en la posición i
     */
    @Override
    public T get(int i) {
        return listSize > 0 && i > 0 && i < listSize ? array[i] : null;
    }
    /**
     * Método que establece un item de la lista en una posición especifica
     * @param p Objeto que sera establecido en una posición especifica
     * @param i Posición a establecer el objeto
     */
    @Override
    public void set(T p, int i) {
        if(listSize > 0 && i > 0 && i < listSize) {
            array[i] = p;
        }
    }
    /**
     * Método que determina el tamaño de la lista
     * @return Devuelve un entero que indica el tamaño de la lista
     */
    @Override
    public int size() {
        return listSize;
    }
    /**
     * Método que inserta al final de la lista
     * @param p Objeto a insertar
     * @return Devuelve la propia lista enlazada
     */
    @Override
    public IList< T > addLast(T p) {
        if(listSize == array.length) {
            adjustCapacity(2 * listSize);
        }
        array[listSize] = p;
        listSize++;
        return this;
    }
    /**
     * Método que permite agregar un objeto en un posición
     * arbitraria de la lista
     * @param p Objeto que se quiere agregar a la lista
     * @param i posición en la cual se quiere agregar
     * @return Devuelve la propia lista enlazada
     */
    @Override
    public IList< T > add(T p, int i) {
        if(listSize == array.length) {
            adjustCapacity(2 * listSize);
        }
        for(int j = listSize - 1; j >= i; j--) {
            array[j + 1] = array[j];
        }
        array[i] = p;
        listSize++;
        return this;
    }
    /**
     * Método que elimina un elemento dado un objeto
     * @param p Objeto a eliminar
     */
    @Override
    public void remove(T p) {
        for(int i = 0; i < listSize; i++) {
            if(array[i].equals(p)) {
                remove(i);
                break;
            }
        }
    }
    /**
     * Método que remueve un elemento de la lista
     * @param i Posición o índice a eliminar de la lista
     */
    @Override
    public void remove(int i) {
        for(int j = i; j < listSize; j++) {
            array[j] = array[j + 1];
        }
        listSize--;
    }
    /**
     * Devuelve si esta o no
     * @param p Objeto a verificar si esta en la lista
     * @return Devuelve true si p esta en la lista false sino
     */
    @Override
    public boolean contains(T p) {
        for(int i = 0; i < listSize; i++) {
            if(array[i].equals(p)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Implementación de la interface iterable que permite recorrer la lista
     * con ciclos estilo for - each
     * @return Devuelve el iterador para recorrer la lista
     */
    @Override
    public Iterator< T > iterator() {
        // Creación de un objeto anónimo para retornarlo
        return new Iterator< T >() {
            /** Contador para referenciar el elemento que actualmente se itera */
            private int i = 0;
            /**
             * Método que indica si existen mas elementos a recorrer o no
             * @return True si existen mas elementos para recorrer y false en caso contrario
             */
            public boolean hasNext() {
                return i < listSize;
            }
            /**
             * Devuelve el elemento donde se encuentra parado el iterador y lo avanza
             * @return
             */
            public T next() {
                i++;
                return array[i - 1];
            }
            /**
             * Método que le permite al iterador remover un elemento de forma segura
             * En la lista hay que tener cuidado cuando se remueve mientras se itera puesto
             * que la eliminación cambia el tamaño de la lista. Este método
             * no se encuentra soportado en esta version.
             */
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
    /**
     * Método privado utilitario encargado de asegurar una capacidad en el arreglo
     * haciendo una copia de los elementos del arreglo viejo en el nuevo
     * @param newCapacity Nueva capacidad del arreglo
     */
    private void adjustCapacity(int newCapacity) {
        T newArray[] = (T[]) new Object[newCapacity];
        for(int i = 0; i < listSize; i++) {
            newArray[i] = array[i];
            array[i] = null;
        }
        array = newArray;
    }
}

La siguiente imagen muestra el diagrama correspondiente a la lista implementada con arreglos.

Lista implementada con arreglos
Figura 1. Lista implementada con arreglos

Observe que el tamaño del arreglo, es decir, su capacidad puede verse incrementada en la operación add, en la cual se puede duplicar el tamaño del arreglo; una practica común es reducir también el tamaño del arreglo cuando se remueve, y es común disminuir el tamaño del arreglo a un cuarto del mismo si la mitad del arreglo esta libre.

Es importante con lo anterior evitar que se hagan secuencias de operaciones que dupliquen – recorren el tamaño del arreglo, es decir add – remove – add – remove y así sucesivamente decrementando sustancialmente el rendimiento del arreglo.

2. Artículos de Interés

Estáticos y Constructores en Java

Los miembros Estáticos y Constructores en Java son muy útiles en el momento de desarrollar código el primero permite establecer una característica de la membresía y el segundo como se inicializan los atributos de los objetos y esto se logra con los constructores, en este post aprenderás ambos conceptos.

Si quieres aprender sobre la historia de Java da clic en este enlace.

1. Miembros estáticos Atributos y Métodos

Los miembros Estáticos en Java utilizan la palabra reservada static y permiten vincular un atributo o método con la clase directamente, en este punto es conveniente notar que los atributos son independientes entre los objetos, de tal manera que cada objeto tiene su propia copia de los atributos, sin embargo con los atributos estáticos no ocurre lo mismo ya que solamente existe una copia de los mismos para todas las instancias de una clase (objetos).

Con los métodos pasa algo similar, de por si los métodos tienen una sola copia ya que todos los objetos acceden a las mismas instrucciones, sin embargo las variables que utilizan si son independientes por objeto, con un método estático se limita el acceso de dicho método solo a atributos estáticos, esta regla impide por ejemplo que un método estático acceda a un miembro no estático.

Al estar asociado los miembros estáticos a la clase los mismos pueden acceder a través del operador punto y utilizando el nombre de la clase, alternativamente puede usarse en objetos.

1.1 Ejemplos de miembros estáticos Atributos y Métodos

Conceptos básicos para escribir código en java que incluyen Clases Métodos y Atributos. Tipos de datos palabras reservadas y escritura básica de código.

A fin de ejemplificar el uso de métodos y atributos estáticos consideremos el siguiente código

public class DemoStatic {
    public int i;
    public static void staticMethod() {
        i = 2;
    }
}

Aquí el acceso a la variable i no esta permitido dentro del método estático y la razón es simple: el atributo no es estático, piense en ello un momento ya que lo anterior implica que se pueda hacer un llamado a staticMethod desde la clase, algo como: DemoStatic.staticMethod(), pero al intentar acceder a la variable i ¿cuál valor de variable se debe usar? sabiendo que el llamado se hizo desde la propia clase, por lo anterior no esta permitido acceder a miembros no estáticos desde métodos estáticos. Lo anterior ocurre con frecuencia cuando se comienza a programar en situaciones como la siguiente.

public class DemoStatic {
    public int i;
    public static void main(String args[]) {
        i = 2;
    }
}

En esencia es el mismo código anterior, excepto que se esta haciendo uso del método main, para subsanar lo anterior hay dos alternativas: convertir la i en atributo estático o instanciar un nuevo objeto de la clase, ambas soluciones se muestran a continuación.

public class DemoStatic {
    public static int i;
    public static void main(String args[]) {
        i = 2;
    }
}
public class DemoStatic {
    public int i;
    public static void main(String args[]) {
        DemoStatic ds = new DemoStatic();
        ds.i = 2;
    }
}

2. Constructores, bloques y ámbitos en Java

Los Constructores en Java son métodos especiales diseñados para inicializar los atributos y se caracterizan por tener el mismo nombre de la clase y retornar nada.

Random r = new Random();

La variable r se encarga de referenciar en memoria a un objeto recién creado de la clase Ramdom y usando el método constructor por defecto.

2.1. Constructores

Existen tres tipos de constructores tradicionales que son:

  1. Los constructores por defecto no reciben ningún parámetro, Java incluye automáticamente en caso que el programador no especifique de forma explicita un constructor.
  2. Constructor con parámetros, que recibe parámetros referentes a los atributos y normalmente los asigna a los mismos.
  3. Constructor de copia, que recibe un objeto de la misma clase y realiza una copia de los atributos del parámetro en el nuevo objeto creado.

Según las reglas de sobrecarga de métodos pueden existir varios constructores, ya que un constructor es un método más.

Las reglas de sobrecarga establecen que los métodos pueden tener el mismo nombre y tipo de retorno siempre y cuando las firmas de los métodos difieran bien sea en tipos de datos de los parámetros o cantidad de parámetros u orden de los mismos o excepciones que arroje el método.

2.2. Inicializaciones de objetos

Un constructor inicializa los atributos que en general pueden inicializarse de tres formas.

  • Inicialización en línea: ocurre cuando en la misma línea de la declaración se inicializa el atributo, esta es la primera inicialización que tiene un atributo..
  • Inicialización en constructor: ocurre cuando se agrega lógica al constructor que inicializa la variable, esta es la última inicialización de un atributo.
  • Inicialización de instancia: ocurre en un bloque de inicialización, estas inicializaciones junto con las inicializaciones en línea se ejecutan en el orden que se encuentren en el código.

Recuerde que no es posible referirse a una variable que no ha sido inicializada. El siguiente código muestra cada uno de estos esquemas de inicialización.

public class InitExamples {
    public InitExamples() {
        // Inicialización en constructor
        i = 5;
    }
    public static void main(String[] args) {
        InitExamples ie = new InitExamples();
        System.out.println(ie.i);
    }
    // Inicialización en línea
    private int i = 3;
    // Bloque de inicialización de instancia
    { i = 4; }
}

¿Qué valor cree que se imprime? Si respondió 5 esta en lo correcto. Observe el bloque de inicialización el cual no tiene etiquetas, ni firmas de prototipos, en este bloque se puede incluir la lógica necesaria para inicializar la variable.

2.3. Ámbitos y variables

Respecto a los bloques de código es importante que el programador reconozca que los bloques son trozos de código encerrados entre llaves y que el ámbito se refiere a los bloques de código donde se puede referir alguna variable.

  • Variables locales: son aquellas declaradas dentro de los métodos y se pueden utilizar en el bloque fueron declaradas y en todo caso no antes de su declaración.
  • Variables de instancia: estas también son conocidas como atributos y se pueden usar en el bloque de su declaración o desde su objeto siempre y cuando cumpla con las reglas de los modificadores de acceso. Una variable de instancia solo se encuentra en memoria al igual que el objeto al cual pertenece, por lo tanto cuando un objeto se queda sin referencias es liberado de la memoria mediante el recolector de basura sucede lo mismo con las variables de instancia.
  • Variables de clase: estas variables son las conocidas como atributos estáticos y esta disponible en el bloque de su declaración y desde los objetos y clases siempre y cuando cumpla con las reglas de los modificadores de acceso. Una variable de clase esta disponible hasta que el programa termina.

Las variables de clase o de instancia pueden no ser inicializadas Java inicializa las variables según el tipo con los siguientes valores por defecto.

Tipo de variableValor inicial
booleanfalse
byte, short, int, long0
float, double0.0
char‘\0000’ (NUL)
Referenciasnull
Tabla 1. Valores iniciales de variables

2.4. Inicializaciones estáticas

Finalmente, y con respecto al tema de inicializaciones y bloques es importante para el programador tener en cuenta que los bloques de inicialización pueden ser marcados con la palabra reservada static de la siguiente manera static { }, este código se ejecutará cuando se cargue la clase y sirve para inicializar atributos estáticos, de manera que en un bloque de inicialización estático solo pueden referenciar en miembros estáticos, similar a como sucede con los método estáticos.

3. Artículos de Interés

Tipos Nativos Objetos Variables y Constantes en Java

Aprende la escancia del manejo de Objetos en Java y diferéncialos de los tipos Nativos. También te explicaremos el uso de Variables y Constantes y la diferencia que existe entre el paso por valor y el paso por referencia.

Si quieres aprender sobre estructura de datos comienza aquí.

1. Referencias, Wrappers y Tipos Nativos Código Java

Cuando aprendes a programar un punto inicial son los Tipos Nativos Objetos Variables y Constantes en Java y es clave poder diferenciarlos, aquí se mostrará todo el detalle necesario para comprender esas diferencias.

1.1. Tipos de Datos Definidos por el Usuario

Aunque Java puede tener muchas clases, que en general se conocen en el mundo de la programación como tipos de datos definidos por el usuario, en realidad Java solo tiene dos tipos de datos: tipos nativos y referencias.

Los tipos nativos son aquellos que almacenan el valor en la misma posición de memoria a la cual se refieren, mientras que las referencias almacenan la dirección de memoria donde reside el objeto.

1.2. Tipos nativos en Java

Para facilitar la comprensión de este concepto vamos a comenzar con los tipos de datos nativos los cuales se pueden ver en la siguiente tabla junto con la cantidad de memoria que ocupan y el rango de valores que admiten, note que un tipo nativo no permiten que se les asigne el valor null.

Tipo de datoTamaño
boolean8 bit (false, true)
byte8 bit (-128 a 127)
short16 bit (-2^15 a 2^15-1)
int32 bit (-2^31 a 2^31-1)
long64 bit (-2^63 a 2^63-1)
float32 bit (±3.4×10^-38 a ± 3.4×10^38)
double64 bit (±1.8×10^-308 a ± 1.8×10^308)
char16 bit (Valores Unicode)
Tabla 2. Tipos de Dato Nativo

En el caso de las referencias, el tamaño que ocupa depende de la arquitectura de la JVM si es de 32 bit o de 64 bit.

Los tipos nativos se caracterizan a su vez ser palabras reservadas, es importante comprender que estos tipos nativos no representan clases u objetos y que Java a incluido versiones objetuales de los tipos nativos, así por ejemplo existe Double e Integer.

2. Constantes en Java

Tipos Nativos Objetos Variables y Constantes en Java tienen una importancia crucial en la codificación en Java en esta sección aprenderás sobre el manejo de las constantes.

2.1. Constantes numéricas

Las constantes numéricas que aparecen en el código son tratadas con int si estas no tienen un punto, en caso que lo tengan son tratadas como constantes numéricas de tipo Double, así mismo se puede forzar a interpretar una constante entera como long si la constante tiene el sufijo L o l (se aconseja utilizar el primero ya que el segundo se asemeja mucho a un carácter 1, en el caso de las constantes numéricas de punto flotante se puede forzar a interpretarlas como float si se utiliza como sufijo la letra F o f y finalmente el sufijo para double es d o D, note que todos estos sufijos hacen referencia a tipos nativos y no a los Wrapper correspondientes. Para el caso del tipo de dato char es importante reconocer que los caracteres individuales se encierran entre comillas dobles mientas que las cadenas de caracteres se encierran en comillas dobles.

2.2. Literales

Las constantes numéricas, es decir, valores fijos numéricos que aparecen el código se denominan literales (también aplica para valores de texto) y Java automáticamente los trata como int si los mismos no tienen punto decimal, en caso que tengan punto decimal se tratan como double, así mismo los literales pueden usar el carácter _ como separador, el prefijo 0 para indicar base octal, el predijo 0x o 0X para base hexadecimal y el prefijo 0b o 0B para base binaria. Algunos ejemplos de literales se muestran a continuación.

long max = 1234567890123; // No permitido, el valor literal supera el tamaño del int
double d1 = _1000.00; // El separador no es permitido al principio
double d2 = 1000.00_; // El separador no es permitido al final
double d3 = 1000_.00; //El separador no es permitido al lado del punto decimal
double d4 = 1_00_0.0_0; // Válido

3. Creación de Objetos en Java

Tipos Nativos Objetos Variables y Constantes en Java tiene una similitud, esta sección explica los objetos y es algo que normalmente los programadores incluso los experimentados no diferencian con claridad, a continuación aprenderás uno de los grandes secretos: las referencias.

En la creación de un objeto es necesario comprender que realmente se crean dos espacios de memoria diferentes, uno para la referencia y otro para el objeto.

Cuando un objeto se queda sin referencias es habilitado para que el recolector de basura libere la memoria asociada a dicho objeto automáticamente, y lo anterior tiene sentido porque si un objeto se queda sin referencias no es posible acceder desde ningún punto del código, el gráfico a continuación ilustra la relación entre referencia y objeto para el siguiente código .

Person p1 = new Person()
Relación entre objetos y referencias
Figura 1. Relación objetos y referencias

Algo importante para notar es que una referencia se crea cuando se declara en cambio el objeto se crea cuando se usa el operador new, por lo tanto se debe comprender que la referencia almacena la dirección de memoria de donde se encuentra el objeto, realmente en Java todas las variables son referencias, es decir, almacenan direcciones de memoria, excepto para los tipos nativos.

4. Paso por valor y paso por referencia

Lo anterior nos lleva a explorar lo que sucede cuando pasamos parámetros a métodos ya que existen dos formas: paso por valor que aplica para atributos nativos y paso por referencia, que aplica para los objetos, a fin de explorar este concepto consideremos el siguiente código.

public static void main(String... args) {
    int a1 = 1;
    m1(a1);
    System.out.println("a: " + a1);
    Integer i = new Integer(5);
    m2(i);
    System.out.println("Integer i: " + i);
    String s = "hi";
    m3(s);
    System.out.println("s: " + s);
    StringBuffer sb = new StringBuffer("abc");
    m4(sb);
    System.out.println("sb: " + sb);
}
public static void m1(int a) {
    a = a + 1;
}
public static void m2(Integer i) {
    i = i + 1;
}
public static void m3(String s) {
    s = s + "1";
}
public static void m4(StringBuffer s) {
    s.append("d");
}

La pregunta es ¿qué se imprime? Lo ideal es revisar con calma antes de contestar. Ahora vamos con cada impresión a revisar el fondo.

4.1. Paso por valor

  • Primera impresión: la primera impresión trata la pregunta si el método m1, es decir, si este método afecta la variable externa, la respuesta es no, debido a que la variable a se pasa por valor al ser de un tipo nativo, termina sucediendo que la variable a del main contiene un valor independiente de la variable a del método m1, por tanto la modificación de la variable a no afecta la variable externa, de hecho el parámetro del método m1 podría incluso tener otro nombre.
  • Segunda impresión: este segundo caso tiene un poco más de truco. Si nos basamos en la teoría aprendida pensaríamos que si se modifica puesto que la variable no hace referencia a un tipo nativo sino a un wrapper, sin embargo en el momento dela suma, la variable i debe ser convertida de Integer a int para poder realizar la suma. Esta característica de Java se llama autoboxing y le permite al lenguaje escribir expresiones con tipos nativos o wrappers de forma equivalente para el programador.
  • Tercera impresión: esta impresión también tiene un pequeño truco, puesto que al igual que el caso anterior pensaríamos que se ve afectada la variable externa, sin embargo, esto no sucede ya que String es una clase con comportamientos muy diferentes al resto de clases de Java. Los String en Java son inmutables y se almacenan en una parte especial de la memoria, es decir, una vez definido un String este no cambiará jamás, por lo tanto el append que se observa en el método m3 no modifica el String y por esta razón el valor externo no cambia.

4.2. Paso por referencia

  • Cuarta impresión: esta impresión asociada al método m4 es el único que en realidad es un paso por referencia de manera que lo que hagamos dentro de este método afectará la variable externa ya que ambas variables sb y s referencia el mismo espacio de memoria, esto también se puede notar ya que solamente existe un new, es decir un objeto tipo StringBuffer en este código.

5. Manejo de Strings

Ahora que conoces los Tipos Nativos Objetos Variables y Constantes en Java es hora de entender una de las clases más importantes en Java y de hecho en cualquier lenguaje, los String que permiten manejar cadenas de texto.

Como particularidad de los String cabe anotar que los objetos de esta clase se pueden representar directamente en el código, por ejemplo "hola".length() note que en este caso no fue necesario hacer un nuevo objeto tipo String para invocar el método length().

5.1. Referencias tipo String

A fin de tener presente los puntos más importantes acerca del manejo de referencias, el programador de Java debe recordar que las referencias de tipos Wrappers y de tipo String tienen un manejo diferente al resto de referencias, las primeras por que les aplica el concepto de autoboxing y las segundas por que las cadenas de texto son almacenadas de forma inmutable, es decir, una vez definido un String este no puede cambiar durante la ejecución del programa, para ejemplificar este último concepto de la inmutabilidad de los String considere el siguiente código.

public static void main(String... args) {
    String str1 = "hola";
    String str2 = "hola";
    String str3 = "hola".trim();
    String str4 = "hola   ".trim();
    System.out.println(str1 == str2);
    System.out.println(str1 == str3);
    System.out.println(str1 == str4);
}

Uno pensaría que la primera impresión es true debido a que son referencias diferentes, pero dado que Java almacena los String de forma especial y en una zona especial de la memoria, la asignación de str1 y str2 terminan apuntando a la misma dirección de memoria.

La segunda impresión sigue siendo verdadero debido a que la función trim() que recorta los espacios en blanco al principio y final de una cadena, no realiza en realidad ninguna operación, debido a que no hay nada que recortar, pero algo curioso sucede con la tercera impresión y en este caso arroja false indicando que la referencia str4 termina apuntando a un lugar diferente de memoria, y por lo tanto terminan existiendo en memoria dos objetos String con la misma información hola, note que esto sucede porque trim() no puede modificar el objeto original y cuando lo modifica deja el resultado en una posición de memoria nueva y para ello no determina si ese objeto String ya existia.

5.2. Errores comunes con Strings

Note que debido a estas características de Java, un programa puede proliferar rápidamente la cantidad de String que tiene creados. Una nota final de los String y esto solamente le sucede a esta clase, y es que no requiere un new, no en el código anterior que se realizaron asignaciones sin necesidad de crear un objeto nuevo y esto es porque Java es capaz de tratar como objetos a las cadenas de texto directamente sobre el código, por lo mismo códigos como el siguiente funcionan correctamente: "hola".trim(). Respecto a la primera impresión del código anterior, que sucedería si en lugar de ser String fueran Integer? y para ello considere el siguiente código.

public static void main(String... args) {
    Integer i1 = 10;
    Integer i2 = 10;
    Integer i3 = new Integer(10);
    Integer i4 = new Integer(10);
    System.out.println(i1 == i2);
    System.out.println(i3 == i4);
}

Este código realmente puede ser muy desconcertante, ¿qué imprimirá?

La respuesta es que la primera impresión es true y la segunda es false, la respuesta es la forma en que se crearon los objetos, el primero se hizo usando la característica de autoboxing (recuerde que esta es la habilidad que tiene Java automáticamente de pasar del tipo nativo al Wrapper según se necesite), por lo tanto en la primera ocasión los objetos apuntan a la misma dirección, este caso funciona de forma similar a los String mientras que en la segunda forma los Integer son creados como objetos primero (lo cual hace que ambas referencias i3 e i4 apunten a direcciones diferentes) y luego se inicializan los valores de estas referencias respectivamente.

6. Destrucción de objetos

Los objetos son puestos en un lugar de memoria llamado Memory Heap. Java tiene un hilo de baja prioridad denominado Garbage Collector que elimina objetos que ya no son referenciados.

Se puede invocar manualmente usando System.gc() aunque nada garantiza que se libere la memoria en un momento dado del tiempo, llamar a este método es meramente una sugerencia para Java. Similar a como los constructores crean objetos los destructores se encargan de eliminarlos, el siguiente método se invoca cuando un objeto es elegido por el recolector y puede personalizar la liberación de recursos. En realidad no es muy usado, sin embargo recuerde que este método nunca se llama dos veces: protected void finalize();

7. Artículos de Interés

Paquetes en Java

Todo lo que necesitas saber de paquetes de Java lo encuentras en esta página web, esperamos que sea de utilidad.

Si quieres saber más sobre la historia y funcionamiento de Java visita este link.

1. Funcionamiento de los paquetes en java

Las clases realmente conforman activos de las organizaciones y de hecho muchas de ellas proveen valiosos mecanismos para reutilizar código, sin embargo, miles de clases se producen y debe existir una forma para organizarlas, dicha forma se conoce como empaquetado y el mismo funciona de manera jerárquica.

2. Generalidades de los paquetes

Los paquetes entonces no son más que contenedores de clases y permiten a los programadores agruparlas de forma lógica. Respecto a los paquetes hay dos cosas que quizás no son muy conocidas pero es importante tenerlas en cuenta a la hora de programar, la primera trata del hecho que en todo programa siempre se encuentra importado por defecto el paquete java.lang en este paquete se encuentran las utilidades y clases básicas del propio core de Java.

Por ejemplo, clases para el manejo de número o cadenas de texto y la segunda es que al estar las clases contenidas en paquetes se crea un nombre de clase que incluye la jerarquía de paquetes donde se encuentra.

3. Ejemplos de paquetes

La clase ArrayList se encuentra en java.util.ArrayList, a este último se le conoce como nombre completamente cualificado. Note que los nombres de los paquetes se separan por punto y que estos nombres al ser jerárquicos se suelen nombrar como nombres de dominio al revés. Por ejemplo el paquete com.ochoscar
También es importante indicar en nuestros archivos y programas que clases utilizamos, esto haciendo uso de la palabra reservada import

4. Errores comunes con paquetes

Cuando realizamos importaciones es conveniente tener presente que no hayan validaciones redundantes de clases que teniendo el mismo nombre se encuentren en paquetes diferentes, por ejemplo, la clase Date que se encuentra tanto en el paquete java.util como en el paquete java.sql. Considere el siguiente código.

import java.util.Date;
import java.sql.Date;
public class DemoImport {
    public static void main(String args[]) {
        Date date;
    }
}

Aquí la importación falla al tener la misma clase desde paquetes diferentes, pero ¿qué hacer si se quisiera instanciar un objeto del paquete java.util y a la vez del paquete java.sql? La mejor opción aquí es utilizar los nombres completamente cualificados como se muestra a continuación.

import java.util.Date;
public class DemoImport {
    public static void main(String args[]) {
        java.sql.Date date;
    }
}

5. Comodines wildcards y subpaquetes

Respecto a las importaciones hay dos temas importante, el primero se conoce como wildcard (*) y no es otra cosa que un comodín para importar todas las clases de un paquete, debemos tener cuidado por que el wildcard no incluye subpaquetes, un ejemplo de uso del wildcard sería import java.util.*;, a manera de nota es recomendable notar que los wildcard a su vez pueden producir conflictos de importación y el segundo punto a tener en cuenta son las importaciones estáticas que sirven para importar en el archivo los miembros estáticos definidas en otra clase, esto ahorra el hecho de referirnos a estos miembros usando el nombre de la clase, para referencia observe el siguiente ejemplo.

import static java.lang.Math.*;
public class DemoImportStatic {
    public static void main(String args[]) {
        double r = 2;
        double a = PI * r * r;
    }
}

Hasta este punto se han visto los componentes esenciales de los archivos de Java, es importante que estén en un orden adecuado para que puedan ser compilados por Java.

Se debe comenzar con las declaración del paquete, las importaciones, la definición de la clase y su contenido, este último normalmente se escribe primero los atributos y luego los métodos, teniendo siempre presente la correcta tabulación, los comentarios de documentación y los nombres adecuados para los identificadores.

6. Artículos de Interés

Colas en Java

Las colas son estructuras de datos similares a las pilas pero con un comportamiento diferente, esta también es una estructura básica puesto que se usa en muchos algoritmos y situaciones en programación.

Visita la página de listas para mayor detalle sobre esta estructura básica.

1. ¿Qué es una cola?

Una cola es una estructura de datos enlazada que sigue una disciplina FIFO (First In, First Out – Primero en entrar, Primero en salir) en la cual el primer elemento que entra es el primero en ser retirado, adicionalmente la estructura, aunque continua siendo una estructura lineal no se puede manipular cualquier elemento como sucedía con las listas, en las colas solamente se puede acceder al elemento que esta en la parte frontal, de manera que si se quiere alcanzar el último elemento, primero se requerirá desencolar todos los otros elementos. La siguiente figura muestra gráficamente una cola.

Cola enlazada
Figura 1. Colas

Las colas tienen muchos usos en informática, un ejemplo sencillo consiste en la representación de una fila de un banco o el procesamiento secuencial de una serie de actividades, por ejemplo el envío de notificaciones de correo electrónico en una aplicación transaccional.

2. Implementación de una cola

La implementación de una cola enlazada utiliza la misma clase Node que se utilizó en las listas enlazadas, la diferencia radica que en los métodos que se utilizan para manipular la cola y que se resumen a continuación:

  • add: este método se encarga de agregar un nuevo elemento en la parte de atrás (último) de la cola.
  • remove: este método remueve el elemento del frente de la cola (primer elemento) y lo devuelve.

2.1. Interface

A continuación se muestra el código correspondiente a las operaciones de una cola con su respectiva documentación.

package queues;
/**
 * Provee las acciones que se pueden realizar con una cola
 * @author ochoscar
 * @param < T > Tipo genérico de los cuales se almacenaran elementos dentro de la cola
 */
public interface IQueue< T > {
    /**
     * Método que verifica si la cola esta vacía
     * @return Devuelve true si la cola esta
     * vacía y false en caso contrario
     */
    public boolean isEmpty();
    /**
     * Método que determina el tamaño de la cola
     * @return Devuelve un entero que indica el tamaño de la cola
     */
    public int size();
    /**
     * Agrega un elemento al final de la cola
     * @param item Item de tipo genérico a ser agregado a la cola
     * @return Devuelve la misma cola que llamo al método
     */
    public IQueue add(T item);
    /**
     * Remueve un elemento del frente de la cola y lo devuelve para su manipulación
     * @return El elemento que se encontraba al frente de la cola
     */
    public T remove();
}

2.2. Clase Cola en Java

Las Colas de datos en Java se pueden implementar con la clase y consideraciones que se muestran a continuación.

La implementación de la interface anterior se encuentra en el siguiente código. Observe que se referencia tanto el primer como el último elemento a fin de realizar las operaciones de agregado y eliminación de forma óptima en cuanto a rendimiento se refiere. Algo importante también es que las operaciones de la cola no requieren de ciclos, por lo tanto la estructura se vuelve supremamente eficiente y así mismo no aporta mayor valor realizar una cola con arreglos, aunque es perfectamente posible.

package queues;
/**
 * Clase que encapsula las diferentes acciones de una cola enlazada
 * @author ochoscar
 * @param < T > Tipo genérico que contra los item de la cola
 */
public class Queue< T > implements IQueue< T > {
    ////////////////////
    // Atributos
    ////////////////////
    /** Referencia al primer nodo de la cola */
    private Node< T > front;
    /** Referencia al ultimo elemento de la cola */
    private Node< T > back;
    /** Tamaño de la cola */
    private int queueSize = 0;
    ////////////////////
    // Métodos
    ////////////////////
    /**
     * Método que verifica si la cola esta vacía
     * @return Devuelve true si la cola esta
     * vacía y false en caso contrario
     */
    @Override
    public boolean isEmpty() {
        return queueSize == 0;
    }
    /**
     * Método que determina el tamaño de la cola
     * @return Devuelve un entero que indica el tamaño de la cola
     */
    @Override
    public int size() {
        return queueSize;
    }
    /**
     * Agrega un elemento al final de la cola
     * @param item Item de tipo genérico a ser agregado a la cola
     * @return Devuelve la misma cola que llamo al método
     */
    @Override
    public IQueue add(T item) {
        Node< T > newNode = new Node(item, null);
        if(queueSize == 0) {
            front = back = newNode;
        } else {
            back.setNext(newNode);
            back = newNode;
        }
        queueSize++;
        return this;
    }
    /**
     * Remueve un elemento del frente de la cola y lo devuelve para su manipulación
     * @return El elemento que se encontraba al frente de la cola
     */
    public T remove() {
        if(queueSize == 0) return null;
        T item = front.getItem();
        front = front.getNext();
        if(front == null) back = null;
        queueSize--;
        return item;
    }
}

3. Artículos de Interés

Pilas en Java

Las pilas en Java junto con las listas constituyen una estructura básica de datos puesto que hay muchos comportamientos que obedecen al apilamiento de ítems.

Visite la sección de listas para encontrar detalles de estas estructuras de datos.

1. ¿Qué es una pila?

Una pila es una estructura de datos enlazada que sigue una disciplina LIFO (Last In, First Out – Último en entrar, Primero en salir) en la cual el ultimo elemento que entra es el primero en ser retirado, adicionalmente la estructura, aunque continua siendo una estructura lineal no se puede manipular cualquier elemento como sucedía con las listas, en las pilas solamente se puede acceder al elemento que esta en la cima, de manera que si se quiere alcanzar el elemento de la base, primero se requerirá desapilar todos los elementos. La siguiente figura muestra gráficamente una pila.

Pila enlazada
Figura 1. Pilas

Las pilas tienen muchos usos en informática, un ejemplo sencillo consiste en la pila de llamados al sistema, en la cual se apilan y desapilan los llamados a los métodos a medida que se invocan los métodos o se retorna de ellos, respectivamente.2. Implementación de una pila

La implementación de una pila enlazada utiliza la misma clase Node que se utilizó en las listas enlazadas, la diferencia radica que en los métodos que se utilizan para manipular la pila y que se resumen a continuación:

  • push: este método se encarga de agregar un nuevo elemento a la cima de la pila.
  • pop: este método remueve el elemento de la cima y lo retorna.
  • peek: este método se encarga de retornar el elemento de la cima sin eliminarlo de la pila.

1.1. Interface de las Pilas

A continuación se muestra el código correspondiente a las operaciones de una pila con su respectiva documentación.

package stacks;
/**
 * Provee las acciones que se pueden realizar con una pila
 * @author ochoscar
 * @param< T > Tipo genérico de los cuales se almacenaran elementos dentro de la pila
 */
public interface IStack< T > {
    /**
     * Método que verifica si la pila esta vacía
     * @return Devuelve true si la pila esta
     * vacía y false en caso contrario
     */
    public boolean isEmpty();
    /**
     * Método que determina el tamaño de la pila
     * @return Devuelve un entero que indica el tamaño de la pila
     */
    public int size();
    /**
     * Agrega un elemento en la cima de la pila
     * @param item Item de tipo genérico a ser agregado a la pila
     * @return Devuelve la misma pila que llamo al método
     */
    public IStack push(T item);
    /**
     * Remueve un elemento de la cima de la pila y lo devuelve para su manipulación
     * @return El elemento que se encontraba en la cima de pila
     */
    public T pop();
    /**
     * Devuelve un elemento de la cima de la pila sin removerlo
     * @return El elemento que se encontraba en la cima de pila
     */
    public T peek();
}

1.2. Clase Pila en Java

La implementación de la interface anterior se encuentra e n el siguiente código. Observe que solamente es necesario referenciar al elemento top o de la cima de la pila para realizar todas las operaciones. Algo importante también es que las operaciones de la pila no requieren de ciclos, por lo tanto la estructura se vuelve supremamente eficiente y así mismo no aporta mayor valor realizar una Pila con arreglos, aunque es perfectamente posible.

package stacks;
/**
 * Clase que encapsula las diferentes acciones de una pila enlazada
 * @author ochoscar
 * @param< T > Tipo genérico que contra los item de la pila
 */
public class Stack< T > implements IStack< T > {
    ////////////////////
    // Atributos
    ////////////////////
    /** Referencia a la cima de la pila */
    private Node< T > top;
    /** Tamaño de la cola */
    private int stackSize = 0;
    ////////////////////
    // Métodos
    ////////////////////
    /**
     * Método que verifica si la pila esta vacia
     * @return Devuelve true si la pila esta
     * vacía y false en caso contrario
     */
    @Override
    public boolean isEmpty() {
        return stackSize == 0;
    }
    /**
     * Método que determina el tamaño de la pila
     * @return Devuelve un entero que indica el tamaño de la pila
     */
    @Override
    public int size() {
        return stackSize;
    }
    /**
     * Agrega un elemento en la cima de la pila
     * @param item Item de tipo genérico a ser agregado a la pila
     * @return Devuelve la misma pila que llamo al método
     */
    @Override
    public IStack push(T item) {
        top = new Node<>(item, top);
        stackSize++;
        return this;
    }
    /**
     * Remueve un elemento de la cima de la pila y lo devuelve para su manipulación
     * @return El elemento que se encontraba en la cima de pila
     */
    @Override
    public T pop() {
        if(stackSize == 0) return null;
        T item = top.getItem();
        top = top.getNext();
        stackSize--;
        return item;
    }
    /**
     * Devuelve un elemento de la cima de la pila sin removerlo
     * @return El elemento que se encontraba en la cima de pila
     */
    @Override
    public T peek() {
        return top != null ? top.getItem() : null;
    }
}

2. Artículos de Interés

Listas Enlazadas en Java

Las listas enlazadas en Java constituyen la base de las estructuras de datos y sustentan una gran cantidad de algoritmos, en este artículo se explicaran estas listas basadas en memoria estática y memoria dinámica.

Repasa los conceptos fundamentales de Java en este vínculo.

1. Necesidad

Las listas enlazadas surgen de la necesidad de manejar de forma más eficiente la memoria, debido a que los arreglos nativos, si bien proporcionan toda la funcionalidad requerida respecto al manejo de listas tiene un gran inconveniente y es que todas sus posiciones se encuentran contiguas (juntas) en memoria, una a continuación de la otra. Suponga entonces que se crea un arreglo de gran tamaño, afortunadamente en Java cada casilla contendrá una referencia a un objeto, es decir cada casilla tiene un tamaño de 32 o 64 bits dependiendo de la arquitectura de la máquina, sin embargo, si el arreglo es muy grande es posible que no se disponga de un gran bloque de memoria contigua para albergar el arreglo.

El problema de encontrar este espacio de memoria disponible empeora con el tiempo que lleve ejecutándose el programa, puesto que la creación de objetos y su recolección ocasionan huecos en la memoria en un fenómeno conocido como fragmentación de la memoria (muy parecido al que sucede en el disco duro, conocido como fragmentación del disco). La Figura 1. muestra un ejemplo de items almacenados en casillas de un arreglo.

Arreglo tradicional
Figura 1. Arreglo tradicional

La idea tras la lista enlazada es permitir que los items que se almacenan en las casillas no tengan que estar necesariamente uno detrás del otro, y para lograr esto las listas enlazadas se forman usando el concepto de clase autoreferenciada que se muestra a continuación.

2. Clases autoreferencias

Las listas enlazadas y muchas otras estructuras de datos se forman utilizando clases autoreferenciadas que no es otra cosa que una clase que contiene al menos un atributo cuyo tipo coincide con la misma clase, en otras palabras una clase autoreferenciada tiene una referencia a un objeto del mismo tipo de la clase.

En la Figura 2, se muestra un ejemplo de una clase autoreferenciada denominada Node que representa una casilla de una lista enlazada, observe como el nodo tiene un espacio de memoria para referenciar el objeto contenido en la casilla y una referencia al nodo siguiente.

Nodo de lista enlazada
Figura 2. Nodo lista enlazada

3. Estructura de una Lista Enlazada

Las listas enlazadas aprovechan el concepto de clases autoreferenciadas para encadenar nodos y permitir armar una lista, observe que únicamente se requiere saber cual es el primer nodo de la lista, puesto que avanzando a través de la referencias next de cada nodo se puede llegar a cualquier otro nodo en la lista. El hecho de conocer la primera posición de la lista es algo que nos es familiar, ya que en el caso de los arreglos el nombre del arreglo realmente es un apuntador al primer elemento de la lista.

Observe que la lista tal como se plantea resuelve el problema de memoria mencionado anteriormente, es decir, al estar los nodos desligados los unos de los otros ya no es necesario para grandes arreglos encontrar un bloque contiguo de memoria, infortunadamente esta solución genera un nuevo inconveniente y es el desplazamiento por la lista, supongamos que se desea acceder a la casilla tercera de la lista, será necesario entonces a partir de la primera casilla iterar para posicionar una referencia que termine apuntando a la tercera casilla, esta iteración toma tiempo y empeora con el tamaño de la lista, situación que no sucede con los arreglos puesto que ubicarse en una posición cualquiera es simplemente una operación aritmética, por lo anterior indexar una posición en la lista enlazada es una operación de tiempo lineal con el tamaño de la lista, en cambio en el arreglo es una operación de tiempo constante.

El programador debe elegir adecuadamente su estructura de datos de tal manera que supla las necesidades del algoritmo, teniendo en cuenta también aspectos de rendimiento.

Lista enlazada
Figura 3. Lista enlazada

4. Implementación de Lista Enlazada

Basados en el gráfico de la Figura 3, podemos realizar la implementación de una lista enlazada, para ello consideraremos tres clases

  • Interface: especifica los métodos públicos que implementaremos para las listas.
  • Nodo: implementación de la clase autoreferenciada para representar las casillas de la lista enlazada.
  • Lista: implementación de todos los métodos y utilidades de la lista enlazada.

El código que se muestra a continuación implementa las tres clases anteriores. Note que la implementación de la interface pude darse con diversas técnicas y formas, no solo con clases autoreferenciadas, de tal manera que para la misma interface se puede tener diferentes implementaciones, la primera que se mostrará es la lista enlazada, y la segunda la lista implementada con arrays.

4.1. Interface

Esta interface representa las operaciones habituales con la lista, observe algo interesante en el caso de los métodos add, los cuales devuelven una lista enlazada, esto a primera vista puede parecer extraño, pero realmente la idea es devolver la misma lista que llamo el método add y de esta forma habilitar el encadenamiento de llamados a métodos de la lista, por ejemplo, se pueden hacer llamados así list.add(obj1).add(obj2) y asi sucesivamente.

package lists;
/**
 * Provee las acciones que se pueden realizar con una lista
 * @author ochoscar
 * @param < T > Tipo genérico de los cuales se almacenaran elementos dentro de la lista
 */
public interface IList< T > {
    /**
     * Método que verifica si la lista esta vacía
     * @return Devuelve true si la lista esta
     * vacía y false en caso contrario
     */
    public boolean isEmpty();
    /**
     * Obtiene el primer item de la lista
     * @return Devuelve la Clase al principio de la lista
     * null en caso que la lista este vacía
     */
    public T getFirst();
    /**
     * Obtiene el ultimo elemento de la lista
     * @return Devuelve la ultima Clase en la lista
     * null si la lista esta vacía
     */
    public T getLast();
    /**
     * Devuelve el i - esimo elemento de la lista
     * @param i Posición del elemento a devolver (comienza en 0)
     * @return Devuelve la Clase en la posición i
     */
    public T get(int i);
    /**
     * Método que establece un item de la lista en una posición especifica
     * @param p Objeto que sera establecido en una posición especifica
     * @param i Posición a establecer el objeto
     */
    public void set(T p, int i);
    /**
     * Método que determina el tamaño de la lista
     * @return Devuelve un entero que indica el tamaño de la lista
     */
    public int size();
    /**
     * Método que inserta al final de la lista
     * @param p Objeto a insertar
     * @return Devuelve la propia lista enlazada
     */
    public IList< T > addLast(T p);
    /**
     * Método que permite agregar un objeto en un posición
     * arbitraria de la lista
     * @param p Objeto que se quiere agregar a la lista
     * @param i posición en la cual se quiere agregar
     * @return Devuelve la propia lista enlazada
     */
    public IList< T > add(T p, int i);
    /**
     * Método que elimina un elemento dado un objeto
     * @param p Objeto a eliminar
     */
    public void remove(T p);
    /**
     * Método que remueve un elemento de la lista
     * @param i Posición o índice a eliminar de la lista
     */
    public void remove(int i);
    /**
     * Devuelve si esta o no
     * @param p Objeto a verificar si esta en la lista
     * @return Devuelve true si p esta en la lista false sino
     */
    public boolean contains(T p);
}

4.2. Nodo

La clase que establece la raíz de la auto referenciación es esta y permite además almacenar el objeto que esta contenido en esta casilla de la lista. El código proporcionado muestra que esta clase no ofrece ninguna operación particular, solamente almacena los atributos para enlazar los nodos y así conformar la lista.

package linkedlist;
/**
 * Clase auto referenciada para construir la estructura
 * tipo lista enlazada que contendrá tanto el item almacenado
 * como la referencia al siguiente elemento de la lista
 * @author ochoscar
 */
public class Node< T > {
    ////////////////////
    // Atributos
    ////////////////////
    /** Referencia al tipo T que ocupa el nodo */
    private T item;
    /** Autoreferencia al siguiente elemento */
    private Node< T > next;
    ////////////////////
    // Métodos
    ////////////////////
    /**
     * Constructor por defecto
     */
    public Node() {
    }
    /**
     * Constructor con parámetros
     * @param pItem Item a almacenar en el nodo
     * @param pNext Siguiente elemento en la lista
     */
    public Node(T pItem, Node< T > pNext) {
        item = pItem;
        next = pNext;
    }
    /**
     * Constructor de copia
     * @param pNode Objeto que servirá para realizar la copia de los
     * atributos del nodo
     */
    public Node(Node< T > pNode) {
        item = pNode.getItem();
        next = pNode.getNext();
    }
    /**
     * Método que devuelve el nodo en su
     * representación de string
     * @return Devuelve la representación en String
     */
    @Override
    public String toString() {
        return item.toString();
    }
    /**
     * @return the item
     */
    public T getItem() {
        return item;
    }
    /**
     * @param item the item to set
     */
    public void setItem(T item) {
        this.item = item;
    }
    /**
     * @return the next
     */
    public Node< T > getNext() {
        return next;
    }
    /**
     * @param next the next to set
     */
    public void setNext(Node< T > next) {
        this.next = next;
    }
}

4.3. Lista Enlazada

La clase lista implementa no solo la interface de lista sino también la interface iterator que permite devolver un objeto Iterador con el cual se recorrerá la lista, este objeto es utilizado automáticamente por java en la instrucción for - each que corresponde a la siguiente sintaxis for(Obj obj : list)

package lists;
import java.util.Iterator;
/**
 * Clase que encapsula las diferentes acciones de una lista enlazada
 * @author ochoscar
 * @param < T > Tipo genérico que contra los item de la lista
 */
public class LinkedList< T > implements IList< T >, Iterable< T > {
    ////////////////////
    // Atributos
    ////////////////////
    /** Referencia al primer nodo de la lista */
    private Node< T > first;
    /** Tamaño de la lista */
    private int listSize = 0;
    ////////////////////
    // Métodos
    ////////////////////
    /**
     * Obtiene el primer item de la lista
     * @return Devuelve la Clase al principio de la lista
     * null en caso que la lista este vacía
     */
    @Override
    public T getFirst() {
        return first != null ? first.getItem() : null;
    }
    /**
     * Obtiene el ultimo elemento de la lista
     * @return Devuelve la ultima Clase en la lista
     * null si la lista esta vacía
     */
    @Override
    public T getLast() {
        return listSize == 0 ? null : getNode(listSize - 1).getItem();
    }
    /**
     * Devuelve el i - esimo elemento de la lista
     * @param i Posición del elemento a devolver (comienza en 0)
     * @return Devuelve la Clase en la posición i
     */
    @Override
    public T get(int i) {
        if(i >= 0 && i < listSize) {
            return getNode(i).getItem();
        }
        return null;
    }
    /**
     * Método que establece un item de la lista en una posición especifica
     * @param p Objeto que sera establecido en una posición especifica
     * @param i Posición a establecer el objeto
     */
    @Override
    public void set(T p, int i) {
        if(i >= 0 && i < listSize) {
            getNode(i).setItem(p);
        }
    }
    /**
     * Método que determina el tamaño de la lista
     * @return Devuelve un entero que indica el tamaño de la lista
     */
    @Override
    public int size() {
        return listSize;
    }
    /**
     * Método que inserta al final de la lista
     * @param p Objeto a insertar
     * @return Devuelve la propia lista enlazada
     */
    @Override
    public IList< T > addLast(T p) {
        add(p, listSize);
        return this;
    }
    /**
     * Método que permite agregar un objeto en un posición
     * arbitraria de la lista
     * @param p Objeto que se quiere agregar a la lista
     * @param i posición en la cual se quiere agregar
     * @return Devuelve la propia lista enlazada
     */
    @Override
    public IList< T > add(T p, int i) {
        if(i >= 0 && i <= listSize) {
            Node< T > newNode = new Node();
            newNode.setItem(p);
            // La lista esta vacía
            if(first == null) {
                first = newNode;
            } else {
                // La lista no esta vacía, y van a inserta
                // en la posición 0
                if(i == 0) {
                    newNode.setNext(first);
                    first = newNode;
                } else if(i == listSize) {
                    // Se quiere insertar al final de la lista
                    Node< T > last = getNode(listSize - 1);
                    last.setNext(newNode);
                } else {
                    Node< T > previous = getNode(i - 1);
                    Node< T > current = previous.getNext();
                    newNode.setNext(current);
                    previous.setNext(newNode);
                }
            }
            listSize++;
        }
        return this;
    }
    /**
     * Método que elimina un elemento dado un objeto
     * @param p Objeto a eliminar
     */
    @Override
    public void remove(T p) {
        Node< T > aux = first;
        int i = 0;
        boolean find = false;
        if(contains(p)){
            while(true){
                if(aux.getItem().equals(p)){
                    break;
                }else{
                    aux = aux.getNext();
                    i++;
                }
            }
            remove(i);
        }
    }
    /**
     * Método que remueve un elemento de la lista
     * @param i Posición o índice a eliminar de la lista
     */
    @Override
    public void remove(int i) {
        if(i >= 0 && i < listSize) {
            // La posición a eliminar es valida, se procede a eliminar
            if(i == 0) {
                first = first.getNext();
            } else {
                Node< T > previous = getNode(i - 1);
                previous.setNext(previous.getNext().getNext());
            }
            // Disminuye el tamaño de la lista
            listSize--;
        }
    }
    /**
     * Devuelve si esta o no
     * @param p Objeto a verificar si esta en la lista
     * @return Devuelve true si p esta en la lista false sino
     */
    @Override
    public boolean contains(T p) {
        Node< T > iterator = first;
        while(iterator != null) {
            if(iterator.getItem().equals(p)) {
                return true;
            }
            iterator = iterator.getNext();
        }
        return false;
    }
    /**
     * Método que verifica si la lista esta vacia
     * @return Devuelve true si la lista esta
     * vacia y false en caso contrario
     */
    @Override
    public boolean isEmpty() {
        return first == null;
    }
    /**
     * Método que devuelve la lista en su
     * representación de string
     * @return Devuelve la representación en String
     */
    @Override
    public String toString() {
        String strToReturn = "[ ";
        Node< T > iterator = first;
        while(iterator != null) {
            strToReturn += iterator.toString() + " ";
            iterator = iterator.getNext();
        }
        return strToReturn + "]";
    }
    /**
     * Implementación de la interface iterable que permite recorrer la lista
     * con ciclos estilo for - each
     * @return Devuelve el iterador para recorrer la lista
     */
    @Override
    public Iterator< T > iterator() {
        // Creación de un objeto anónimo para retornarlo
        return new Iterator< T >() {
            /** Ubica un puntero en el primer elemento de la lista a retornar */
            private Node< T > iteratorNode = first;
            /**
             * Método que indica si existen mas elementos a recorrer o no
             * @return True si existen mas elementos para recorrer y false en caso contrario
             */
            public boolean hasNext() {
                return iteratorNode.getNext() != null;
            }
            /**
             * Devuelve el elemento dond se encuentra parado el iterador y lo avanza
             * @return
             */
            public T next() {
                T item = iteratorNode.getItem();
                iteratorNode = iteratorNode.getNext();
                return item;
            }
            /**
             * Método que le permite al iterador remover un elemento de forma segura
             * En la lista hay que tener cuidado cuando se remueve mientras se itera puesto
             * que la eliminación cambia el tamaño de la lista. Este método
             * no se encuentra soportado en esta version.
             */
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
    /**
     * Método que devuelve un nodo dada una posición
     * @param pos Posición de la lista para retornar el nodo
     * @return Devuelve el nodo indicado en el parámetro pos
     */
    private Node< T > getNode(int pos) {
        int i = 0;
        Node< T > iterator = first;
        while(iterator != null) {
            if(i == pos) {
                return iterator;
            }
            i++;
            iterator = iterator.getNext();
        }
        return null;
    }
}

Las listas enlazadas también se pueden disponer como listas doblemente enlazada, donde cada nodo no solamente mantenga una referencia de su siguiente elemento sino también del elemento anterior. A continuación se muestra la implementación de listas usando nodos doblemente enlazados, note que cada nodo tiene dos referencias una apuntando hacía el elemento siguiente y otra apuntando hacía el elemento anterior.

Lista doblemente enlazada
Figura 4. Lista doblemente enlazada

Algunas ventajas de esta lista es que ciertas operaciones de desplazamiento resultan más óptimas al no tener que recorrer siempre la lista completa desde el principio para posicionarse en cualquier elemento, considere en este sentido, por ejemplo, recorrer la lista en orden inverso. Otra lista importante es la Lista Circular que simplemente es aquella que se forma uniendo el principio de la lista con el final y en este caso es común utilizar un nodo ficticio para determinar un primer elemento de la lista

El programador debe elegir adecuadamente su estructura de datos de acuerdo con la naturaleza de los mismos y de esta forma se le facilitaran las operaciones, concentrarse en la lógica de fondo, realizar códigos menos propensos a errores y finalmente con mayor facilidad de mantenimiento.

5. Lista con arreglos

Como se había mencionado anteriormente la lista con arreglos puede ser una implementación de la misma interface IList. La ventaja en este caso es la facilidad para posicionarse en cualquier punto, sin embargo, al estar la lista conformada con arreglos, tiene las desventajas del uso de arreglos que se menciono al principio de esta sección. El arreglo es muy óptimo para indexare, pero cuando se acaba el tamaño es necesario crear otro nuevo con más tamaño y hacer un copiado de los elementos del arreglo original en el nuevo con mayor capacidad, esta operación ralentiza los métodos de la lista. Por esta razón las listas con arreglos deben tener mínimamente dos conceptos incluidos: el tamaño (que comúnmente se refiere al tamaño ocupado) y la capacidad (que es la máxima cantidad de elementos que puede tener el arreglo utilizado en la lista). A continuación, se presenta el código de la lista enlazada usando arreglos.

package lists;
import java.util.Iterator;
/**
 * Lista que usa arreglos nativos para implementar las operaciones de una lista
 * @author ochoscar
 * @param < T > Tipo genérico que contra los item de la lista
 */
public class ArrayList< T > implements IList< T >, Iterable< T >  {
    ////////////////////
    // Atributos
    ////////////////////
    /** Arreglo nativo de la lista que contiene los elementos y su longitud es
     la capacidad máxima de elementos a almacenar*/
    private T array[];
    /** Tamaño de la lista que refleja la cantidad de casillas del arreglo usadas */
    private int listSize;
    ////////////////////
    // Métodos
    ////////////////////
    /**
     * Constructor por defecto que crea la lista con 10 casillas de capacidad
     */
    public ArrayList() {
        array = (T[]) new Object[10];
        listSize = 0;
    }
    /**
     * Constructor especificando la capacidad inicial
     * @param initCapacity Capacidad inicial
     */
    public ArrayList(int initCapacity) {
        array = (T[]) new Object[initCapacity];
        listSize = 0;
    }
    /**
     * Método que verifica si la lista esta vacía
     * @return Devuelve true si la lista esta
     * vacía y false en caso contrario
     */
    @Override
    public boolean isEmpty() {
        return listSize == 0;
    }
    /**
     * Obtiene el primer item de la lista
     * @return Devuelve la Clase al principio de la lista
     * null en caso que la lista este vacía
     */
    @Override
    public T getFirst() {
        return listSize > 0 ? array[0] : null;
    }
    /**
     * Obtiene el ultimo elemento de la lista
     * @return Devuelve la ultima Clase en la lista
     * null si la lista esta vacía
     */
    @Override
    public T getLast() {
        return listSize > 0 ? array[listSize - 1] : null;
    }
    /**
     * Devuelve el i - esimo elemento de la lista
     * @param i Posición del elemento a devolver (comienza en 0)
     * @return Devuelve la Clase en la posición i
     */
    @Override
    public T get(int i) {
        return listSize > 0 && i > 0 && i < listSize ? array[i] : null;
    }
    /**
     * Método que establece un item de la lista en una posición especifica
     * @param p Objeto que sera establecido en una posición especifica
     * @param i Posición a establecer el objeto
     */
    @Override
    public void set(T p, int i) {
        if(listSize > 0 && i > 0 && i < listSize) {
            array[i] = p;
        }
    }
    /**
     * Método que determina el tamaño de la lista
     * @return Devuelve un entero que indica el tamaño de la lista
     */
    @Override
    public int size() {
        return listSize;
    }
    /**
     * Método que inserta al final de la lista
     * @param p Objeto a insertar
     * @return Devuelve la propia lista enlazada
     */
    @Override
    public IList< T > addLast(T p) {
        if(listSize == array.length) {
            adjustCapacity(2 * listSize);
        }
        array[listSize] = p;
        listSize++;
        return this;
    }
    /**
     * Método que permite agregar un objeto en un posición
     * arbitraria de la lista
     * @param p Objeto que se quiere agregar a la lista
     * @param i posición en la cual se quiere agregar
     * @return Devuelve la propia lista enlazada
     */
    @Override
    public IList< T > add(T p, int i) {
        if(listSize == array.length) {
            adjustCapacity(2 * listSize);
        }
        for(int j = listSize - 1; j >= i; j--) {
            array[j + 1] = array[j];
        }
        array[i] = p;
        listSize++;
        return this;
    }
    /**
     * Método que elimina un elemento dado un objeto
     * @param p Objeto a eliminar
     */
    @Override
    public void remove(T p) {
        for(int i = 0; i < listSize; i++) {
            if(array[i].equals(p)) {
                remove(i);
                break;
            }
        }
    }
    /**
     * Método que remueve un elemento de la lista
     * @param i Posición o índice a eliminar de la lista
     */
    @Override
    public void remove(int i) {
        for(int j = i; j < listSize; j++) {
            array[j] = array[j + 1];
        }
        listSize--;
    }
    /**
     * Devuelve si esta o no
     * @param p Objeto a verificar si esta en la lista
     * @return Devuelve true si p esta en la lista false sino
     */
    @Override
    public boolean contains(T p) {
        for(int i = 0; i < listSize; i++) {
            if(array[i].equals(p)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Implementación de la interface iterable que permite recorrer la lista
     * con ciclos estilo for - each
     * @return Devuelve el iterador para recorrer la lista
     */
    @Override
    public Iterator< T > iterator() {
        // Creación de un objeto anónimo para retornarlo
        return new Iterator< T >() {
            /** Contador para referenciar el elemento que actualmente se itera */
            private int i = 0;
            /**
             * Método que indica si existen mas elementos a recorrer o no
             * @return True si existen mas elementos para recorrer y false en caso contrario
             */
            public boolean hasNext() {
                return i < listSize;
            }
            /**
             * Devuelve el elemento donde se encuentra parado el iterador y lo avanza
             * @return
             */
            public T next() {
                i++;
                return array[i - 1];
            }
            /**
             * Método que le permite al iterador remover un elemento de forma segura
             * En la lista hay que tener cuidado cuando se remueve mientras se itera puesto
             * que la eliminación cambia el tamaño de la lista. Este método
             * no se encuentra soportado en esta version.
             */
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
    /**
     * Método privado utilitario encargado de asegurar una capacidad en el arreglo
     * haciendo una copia de los elementos del arreglo viejo en el nuevo
     * @param newCapacity Nueva capacidad del arreglo
     */
    private void adjustCapacity(int newCapacity) {
        T newArray[] = (T[]) new Object[newCapacity];
        for(int i = 0; i < listSize; i++) {
            newArray[i] = array[i];
            array[i] = null;
        }
        array = newArray;
    }
}

La siguiente imagen muestra el diagrama correspondiente a la lista implementada con arreglos.

Lista implementada con arreglos
Figura 5. Lista implementada con arreglos

Observe que el tamaño del arreglo, es decir, su capacidad puede verse incrementada en la operación add, en la cual se puede duplicar el tamaño del arreglo; una practica común es reducir también el tamaño del arreglo cuando se remueve, y es común disminuir el tamaño del arreglo a un cuarto del mismo si la mitad del arreglo esta libre. Es importante con lo anterior evitar que se hagan secuencias de operaciones que dupliquen – recorren el tamaño del arreglo, es decir add – remove – add – remove y así sucesivamente decrementando sustancialmente el rendimiento del arreglo.

6. Artículos de Interés

Ejecución de programas en Java

Según el tipo de aplicación, por ejemplo web, escritorio o móvil se debe seguir una serie de pasos para ejecutar un programa, en este artículo se aborda la ejecución de programas en Java de escritorio desde terminal o consola.

1. Ejecución de programas Java

1.1. Verificación de instalación de Java

Cuando pensamos en ejecutar programas Java para el escritorio requerimos hacerlo desde una consola o terminal, es importante para el ambiente cuente con el comando java instalado y sea accesible desde el lugar o directorio donde se ejecute el comando. En la figura 1 se muestra un ejemplo de ejecución del comando java -version que muestra la versión de la máquina virtual de Java que ejecutará el comando.

Es importante reconocer que también existen otros comandos para compilar como javac o algunas utilidades para revisar la carga de memoria o el comportamiento del procesador y que estas utilidades están disponibles en la carpeta bin de la instalación.

Para este punto lo más normal es compilar y ejecutar los archivos java utilizando un Entorno Integrado de Desarrollo o IDE como Eclipse, NetBeans o IntelliJ.

Verificación de instalación de Java
Figura 1. Ejecución del comando Java

1.2. Ejecución de programa en Java desde la consola

Ahora, una vez que el comando java se encuentra disponible se podrán ejecutar programas java simplemente pasando el archivo .class como parámetro de Java y teniendo presente la jerarquía de paquetes donde se almaceno nuestra clase, por ejemplo la figura 2 muestra la ejecución del programa HolaMundo mostrado en el listado de código 1.

Ejecutar programa con Java
Figura 2. Ejecución de un programa Java
public class HolaMundo {
    public static void main(String args[]) {
        System.out.println("Hola mundo");
    }
}

Aquí se puede comprender el método main que es el punto de entrada para la ejecución de instrucciones en Java y el mismo debe ser estático para que la JVM pueda llamar el método sin necesidad de instanciar la clase HolaMundo, también debe ser público para que la JVM pueda ver el método, debe llamarse main específicamente puesto que con este nombre lo buscará la JVM.

Finalmente pueden pasarse una serie de parámetros a manera de arreglo e String, estos parámetros son enviados en el momento de la ejecución en la línea de comandos, y simplemente mencionar que si se envían parámetros que contengan espacios y estos van a ser interpretados como un solo ítem en el arreglo args deberá escribirse entre comillas dobles.

Para mayor información de clases métodos y atributos de Click aquí.

Alternativamente se pueden ejecutar los programa java si el proyecto es empaquetado como un jar, es decir, como un archivo comprimido java el cual contiene un conjunto de archivos .class y un archivo manifiesto que indica cual de las clases es la principal y que a su vez contiene un método main que será utilizado para iniciar la ejecución de instrucciones. El comando necesario para ejecutar un jar es el siguiente: java -jar ArchivoJAVAJAR.jar. Si el archivo jar no contiene un manifiesto o la clase principal no contiene un main con las consideraciones descritas nos será posible ejecutarlo.

2. Artículos de Interés

Encapsulamiento en Java

Si bien el encapsulamiento es uno de los pilares de la programación orientada a objetos en este artículo se expone las consideraciones especificas para el lenguaje Java.

1. Encapsulamiento

El encapsulamiento en java y es uno de los pilares de la programación orientada a objetos que establece la posibilidad de ocultar la información de las propiedades de un objeto de forma que las mismas no se accedan directamente.

Hasta este momento ya se conoce acerca de la forma de escribir clases y crear objetos, además se ha vinculado la sintaxis de la programación estructurada en el sentido que se cuenta con estructuras para controlar el flujo de un algoritmo, ahora entenderemos como las propiedades (recuerde que también se llaman atributos o campos) pueden ser ocultados para que los objetos no puedan modificarlos directamente.

1.1. Ocultamiento de la información

El objetivo entonces será ocultar la información de la implementación de forma que los usuarios programadores que utilicen la clase no puedan acceder a estos detalles, lo anterior mejorará la abstracción permitiendo que los programadores se enfoquen en usar atributos y métodos seguros con los cuales no puedan generar inadvertidamente daños.

2. Ejemplos

Para comprender estos conceptos consideremos la clase Tiempo.

class Tiempo {
    //////////////////////////
    // Atributos
    //////////////////////////
    private int hora;
    private int minutos;
    //////////////////////////
    // Métodos
    //////////////////////////
    public int getHora() {
        return hora;
    }
    public void setHora(int hora) {
        hora = this.hora;
    }
    public int getMinutos() {
        return minutos;
    }
    public void setHora(int minutos) {
        minutos = this.minutos();
    }
}

En esta clase se puede apreciar como los atributos son privados, lo cual indica que los objetos que creemos de dicha clase no podrán acceder directamente a los atributos, estos objetos aunque si contienen los atributos (hora y minutos) no podrán utilizarlos ya que al ser privados solamente se podrán usar en el mismo archivo y más concretamente en la misma clase donde fueron declarados.

Al parecer no existiría ninguna ventaja al tener los atributos privados puesto que sería mejor tenerlos públicos y evitarse el paso por un método para establecerlo (set) y obtenerlo (get) compare las siguientes dos líneas de código que muestran estas dos posibilidades.

Tiempo t = new Tiempo();
t.setHora(20);
t.hora = 40;

2.2. Comparación privado y público

El primer set de la variable Hora se realiza con el atributo encapsulado y privado mientras que el segundo se realiza con el atributo público, observe que en el segundo caso no existe ninguna forma de evitar asignar un valor inválido tal como esta sucediendo con el valor 40, de esta forma el encapsulamiento ayuda a evitar que los objetos tengan valores inconsistentes.

3. Utilidad del encapsulamiento

Algo adicional que debe conservar en mente y que es algo esencial del encapsulamiento es el hecho de que aísla la representación interna del objeto de su interface, en palabras más sencillas la representación interna dada por los atributos, sus valores y estados están tras una capa de métodos getter y setter, de forma que en una aplicación podría pensar en cambiar la forma como se guarda internamente las horas y decidir.

Por ejemplo, guardarlo con un solo entero, este cambio mejoraría el desempeño de la memoria afectando el desempeño en tiempos, sin embargo quienes hayan usado las clase Tiempo no se verán afectados y seguirán utilizando los mismos métodos getter y setter sin importar que internamente se guarden dos enteros para representar la hora y los minutos o un solo atributo para guardar, por ejemplo la cantidad de minutos que hayan pasado desde la media noche.

Repasa los fundamentos de las clases, métodos y atributos de java.

3. Artículos de Interés

« Entradas anteriores

© 2024 ochoscar's blog

Tema por Anders NorenArriba ↑