Categoría: Programación (Página 2 de 3)

Instalación de VUE

Esta página te enseña todo lo necesario para realizar la Instalación de VUE y comenzar a programar en este framework del frontend.

Si quieres aprende a programar en Java gratis sigue este link.

1. Proceso de instalación de VUE

Algo muy bueno cuando se programa en VUE es que el proceso de instalación es bastante sencillo, los componentes necesarios para realizar la instalación son pocos y todos tienen wizard y asistentes para que la instalación sea muy sencilla, sigue estos simples pasos a continuación para lograrlo.

2. Instalando Node JS

La instalación de Node JS también es muy simple, pero se preguntará por qué este componente es necesario para VUE, y básicamente es por dos razones, la primera porque a través de nodejs (que es una tecnología del backend) se puede montar un servidor que permita montar la página web estática del desarrollo hecho en VUE, esto quiere decir que el proceso de compilación de VUE arroja archivos html, js y css que son los que entrega el servidor nodejs al navegador que lo solicite, y la segunda es porque a través de nodejs se tiene acceso a las herramientas de desarrollo como la consola de comandos y la instalación de paquetes de VUE.

Para instalar nodejs básicamente se requiere descargar el ejecutable según tu sistema operativo linux, mac o windows y seguir el wizard o asistente del instalador.

En la siguiente página puedes encontrar el instalador de nodejs.

Figura 1. Logo de NodeJS

Para verificar que la instalación esta correcta puedes hacerlo ejecutando un comando que muestre la versión de nodejs instalado. Para hacerlo simplemente ejecuta el siguiente comando en una terminal o consola.

npm -v

3. Instalando Visual Studio

No falta mucho para poder programar en VUE, solamente se requiere de forma adicional un editor de texto y aunque en principio se puede usar un simple block de notas siempre es preferible tener algunas capacidades orientadas al código, uno de los más usados para este propósito y que es gratuito es Visual Studio Code cuya instalación es tan simple como la de nodejs.

Figura 2. Logo de Visual Studio Code

En este enlace puedes encontrar el link para descargar el instalador de Visual Studio.

4. Plugins recomendados de Visual Studio

Finalmente, y aunque no es requerido para continuar te recomiendo que instales en Visual Studio en el menú de configuración / extensiones algunos plugins que son muy útiles para poder programar de forma fluida en VUE. Los plugins recomendados son los siguientes:

  • VUE 3 Snippets
  • Vetur
  • npm intellisense
Figura 3. VUE 3 Snippets
Figura 4. Vetur
Figura 5. npm intellisense

5. Instalando VUE CLI

Luego de haber instalado los componentes anteriores falta simplemente instalar la consola de línea de comandos (CLI, por sus siglas) y esto se logra con un simple comando. Para ello abre una consola o terminal, en el caso de windows basta abrir el programa CMD que lo encuentras en el inicio o una terminal si estas en linux o Mac, y a continuación ejecutar el siguiente comando:

npm install -g @vue/cli

El comando npm es el manejador de paquetes (software) de node y con el podemos instalar cientos de librerías o programas, en este caso estamos instalando la consola de vue usando la opción -g que indica que será instalado de forma global. Una vez terminado ya prodrás crear el primer programa en VUE y empezar a programar.

6. Artículos de Interés

Glosario de Términos Informáticos

Este artículo muestra una recopilación de diferentes siglas utilizados en informática a fin de tener una referencia rápida de conceptos muy utilizados en sistemas a manera de Glosario de Términos Informáticos.

Para Aprender Java sigue este link.

Para Aprender VUE sigue este link.

Glosario

  • CSS: Hoja de Estilos en Cascada, permite aplicar estilos gráficos para darle diseño a los diferentes componentes que se encuentran en una página web, entre otras acciones comunes se encuentran la definición de estilos de texto, posiciones, colores, etc.
  • DNS: Servicio de Nombres de Domino, es un sistema o base de datos que funciona para realizar traducciones de un nombre de dominio por ejemplo google.com a su correspondiente IP a fin de ubicar una máquina en una red de datos como Internet.
  • JSON: Notación de Objetos de JS, permite dar formato a datos usando una cantidad mínima de overheading y que a la vez sea fácil de entender por parte de los seres humanos, a diferencia por ejemplo de formatos binarios.
  • HTML: Lenguaje de Marcas de Hypertexto es utilizado para escribir Páginas Web y es interpretado por navegadores que luego permitiran mostrar este contenido en el propio navegador
  • HTTP: Protocolo de Transferencia de Hypertexto utilizado para transmitir paquetes que en su contenido tendran HTML correspondiente a páginas web o peticiones de navegadores web.
  • JS: Javascript es el lenguaje utilizado por los programadores del frontend para escribir la lógica y el comportamiento de las Interfaces Gráficas de usuario mostradas en los navegadores.
  • URL: Localizador Uniforme de Recursos o simplemente la dirección de una página web como google.com
  • XML: Lenguaje Extensible de Marcas, es el conjunto universal de todos los lenguajes de marcas que permiten formatear datos a fin de hacerlos legibles tanto por humanos como por máquinas. En estos formatos se puede describir prácticamente cualquier dato o incluso bases de datos.

Introducción y Características VUE

Esta página encontrarás una Introducción y Características de VUE que te permitirán desarrollar proyectos de frontend en este framework famoso y versátil, a medida que avance en este mundo de la Programación Web encontrará lo fascinante que es, bienvenido!

Para aprender backend en Java siga este vínculo

1. Aplicaciones en SPA

En esta Introducción y Características de VUE lo primero es entender las Single Page Application o SPA es la forma de programar páginas web interactivas en la actualidad, estas páginas web se caracterizan por no requerir la actualización de todo el contenido cada vez que se realiza una acción por parte del usuario.

Para entender este concepto usemos la siguiente imagen como referencia. Aquí se aprecia que el usuario en el computador de la izquierda realiza una petición HTTP en el momento de escribir una URL, por ejemplo midominio.com, esta URL es traducida por el sistema DNS a una IP la cual a la larga corresponde a una máquina o servidor, este servidor a su vez procesa la solicitud y devuelve el correspondiente a la Página Web usando HTML, JS y CSS.

En las Páginas Web tradicionales cada acción del usuario implica que el servidor envíe toda la Página Web y el navegador tenga que renderizar todo lo cual produce un parpadeo de la página, lo cual además de afectar la experiencia del usuario termina siendo muy ineficiente.

Modelo de Páginas Web Tradicionales
Figura 1. Modelo de Páginas Web Tradicionales

2. Algunas definiciones

Antes de continuar es conveniente contar con algunas definiciones de terminología que se han visto hasta el momento.

  • CSS: Hoja de Estilos en Cascada, permite aplicar estilos gráficos para darle diseño a los diferentes componentes que se encuentran en una página web, entre otras acciones comunes se encuentran la definición de estilos de texto, posiciones, colores, etc.
  • DNS: Servicio de Nombres de Domino, es un sistema o base de datos que funciona para realizar traducciones de un nombre de dominio por ejemplo google.com a su correspondiente IP a fin de ubicar una máquina en una red de datos como Internet.
  • JSON: Notación de Objetos de JS, permite dar formato a datos usando una cantidad mínima de overheading y que a la vez sea fácil de entender por parte de los seres humanos, a diferencia por ejemplo de formatos binarios.
  • HTML: Lenguaje de Marcas de Hypertexto es utilizado para escribir Páginas Web y es interpretado por navegadores que luego permitiran mostrar este contenido en el propio navegador
  • HTTP: Protocolo de Transferencia de Hypertexto utilizado para transmitir paquetes que en su contenido tendrán HTML correspondiente a páginas web o peticiones de navegadores web.
  • JS: Javascript es el lenguaje utilizado por los programadores del frontend para escribir la lógica y el comportamiento de las Interfaces Gráficas de usuario mostradas en los navegadores.
  • URL: Localizador Uniforme de Recursos o simplemente la dirección de una página web como google.com
  • XML: Lenguaje Extensible de Marcas, es el conjunto universal de todos los lenguajes de marcas que permiten formatear datos a fin de hacerlos legibles tanto por humanos como por máquinas. En estos formatos se puede describir prácticamente cualquier dato o incluso bases de datos.

3. SPA vs Web Tradicional

La principal diferencia entre el SPA y el Web Tradicional queda establecida en la misma forma en que se crean comunica la página web con el servidor y para ellos vamos a entender por partes la comunicación:

  1. Lo primero que ocurre es que el usuario navega a una URL ya sea escribiendo la dirección en la barra de direcciones del navegador o dando clic en un enlace.
  2. Esta navegación involucra una petición que por ser la primera vez implica diferencia entre la Web Tradicional y SPA, puesto que para ambas se tiene una respuesta consistente en HTML, JS y CSS.
  3. En la interacciones posteriores del usuario y el navegador es que se nota la diferencia, mientras que la web tradicional sigue devolviendo HTML, JS y CSS, un página SPA solo devuelve contenido de datos normalmente formateados con JSON o XML haciendo que no se recargue toda la página sino simplemente la zona de la página que requiere la actualización.

Por lo anterior, el paradigma de SPA y programación reactiva implica actualizaciones más rápidas y eficientes, una mejor experiencia del usuario y comunicaciones optimizadas en tamaños y tiempos. La siguiente imagen ilustra una página web actualizándose en un solo punto producto de una respuesta en JSON.

Actualización de Página web después de la segunda petición usando SPA
Figura 2. Actualización de Página web después de la segunda petición usando SPA

4. Componentes del Framework

Para desarrollar Páginas Web usando SPA se cuenta con muchos framework y uno de los más populares por su facilidad de aprendizaje es VUE que permite trabajar con los lenguajes que el programador de frontend ya conoce como HTML, JS y CSS.

Este framework está diseñado pensando en la experiencia de usuario está muy bien documentado y existen miles de componentes que permiten extenderlo y realizar desarrollos confiables manteniendo la simplicidad necesaria para mantener el código.

La página oficial de VUE la puedes encontrar aquí

5. 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

« Entradas anteriores Entradas recientes »

© 2024 ochoscar's blog

Tema por Anders NorenArriba ↑