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.
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.
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
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.
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.
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.
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!
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
Constructor con parámetros, que recibe parámetros referentes a los atributos y normalmente los asigna a los mismos.
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 variable
Valor inicial
boolean
false
byte, short, int, long
0
float, double
0.0
char
‘\0000’ (NUL)
Referencias
null
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.
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.
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 dato
Tamaño
boolean
8 bit (false, true)
byte
8 bit (-128 a 127)
short
16 bit (-2^15 a 2^15-1)
int
32 bit (-2^31 a 2^31-1)
long
64 bit (-2^63 a 2^63-1)
float
32 bit (±3.4×10^-38 a ± 3.4×10^38)
double
64 bit (±1.8×10^-308 a ± 1.8×10^308)
char
16 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()
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.
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.
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();
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.
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.
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.
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;
}
}
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.
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.
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;
}
}
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.
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.
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.
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.
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.
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.
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.