Etiqueta: programacion (Página 3 de 3)

Listas Enlazadas en Java

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

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

1. Necesidad

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

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

Arreglo tradicional
Figura 1. Arreglo tradicional

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

2. Clases autoreferencias

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

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

Nodo de lista enlazada
Figura 2. Nodo lista enlazada

3. Estructura de una Lista Enlazada

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

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

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

Lista enlazada
Figura 3. Lista enlazada

4. Implementación de Lista Enlazada

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

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

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

4.1. Interface

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

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

4.2. Nodo

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

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

4.3. Lista Enlazada

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

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

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

Lista doblemente enlazada
Figura 4. Lista doblemente enlazada

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

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

5. Lista con arreglos

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

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

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

Lista implementada con arreglos
Figura 5. Lista implementada con arreglos

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

6. Artículos de Interés

Ejecución de programas en Java

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

1. Ejecución de programas Java

1.1. Verificación de instalación de Java

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

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

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

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

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

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

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

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

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

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

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

2. Artículos de Interés

Encapsulamiento en Java

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

1. Encapsulamiento

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

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

1.1. Ocultamiento de la información

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

2. Ejemplos

Para comprender estos conceptos consideremos la clase Tiempo.

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

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

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

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

2.2. Comparación privado y público

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

3. Utilidad del encapsulamiento

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

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

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

3. Artículos de Interés

Control de Flujo en Java

Cuando se desarrollan algoritmos se debe considerar tanto las estructuras de datos como los controles de flujo cuya sintaxis en Java se recopila en este artículo.

1. Control de flujo: instrucciones condicionales

En Java las instrucciones de la programación estructurada están disponibles para controlar el flujo y determinar que instrucciones ejecutar o cuantas veces repetirlas.

Visita la página de historia de java para más detalles introductorios.

La primera instrucción de control es el condicional cuya forma compuesta se muestra a continuación.

// Forma simple del condicional
if(expresión booleana) {
    // Bloque en caso que la expresión sea verdadera
}
// Forma compuesta del condicional
if(expresión booleana) {
    // Bloque en caso que la expresión sea verdadera
} else {
    // Bloque en caso que la expresión sea verdadera
}
// Condicional en escalera
if(expresión booleana 1) {
    // Bloque en caso que la expresión 1 sea verdadera
} else if(expresión booleana 2) {
    // Bloque en caso que la expresión 2 sea verdadera
} else if(expresión booleana 3) {
    // Bloque en caso que la expresión 3 sea verdadera
} else{
    // Bloque en caso que ninguna expresion sea verdadera
}

Los puntos clave a resaltar sobre la instrucción condicional son los siguiente:

  • Los paréntesis en la expresión booleana son obligatorios.
  • No es requerido el uso de la parte del else en el código.
  • Es supremamente importante tener en cuenta que el código dentro de un bloque debe tener sangría aplicada.
  • En la tercera forma del condicional (en escalera) aunque varias expresiones booleanas sean verdaderas solamente se ejecuta el bloque correspondiente a la primera expresión booleana verdadera, así mismo puede haber un número indefinido de segmentos else if.
  • Tenga presente que en muchas ocasiones el operador ternario simplifica la escritura del código, sin embargo, debe balancear dicha simplicidad con la dificultad de leer expresiones muy crípticas.
  • En caso que los bloques tengan una sola instrucción no es necesario usar llaves, no obstante lo anterior es muy recomendado por legibilidad siempre usar llaves.

1.1. Instrucción swtich

Otra instrucción condicional es switch cuya sintaxis se puede apreciar en el siguiente segmento de código.

switch(variable) {
    case CONSTANTE1:
        // código 1
        break;
    case CONSTATE2:
        // código 2
        break;
    default:
        // código por defecto
}

En el caso de la instrucción switch se puede apreciar que la misma es utilizada de forma similar a un if en escalera, estos permiten realizar el control de flujo las bifuraciones del código . Hay que recordar que las instrucciones que se ejecutaran corresponden a la primera comparación verdadera de la variable con las diferentes constantes, igualmente solo se ejecuta un bloque de código que esta marcado desde los dos puntos y hasta el siguiente break, en ocasiones y dado que la instrucción break es opcional, se pueden juntar varios segmentos case para ejecutar un mismo código para cualquiera de las constantes que acompañen dichos segmentos case.

Note también que hay un segmento por defecto y es opcional, este se ejecuta cuando ninguna de las comparaciones con las constantes arrojo un resultado verdadero. Los tipos de datos soportados para la variable son: int, Integer, byte, Byte, short, Short, char, Character, String, y enum.

De otra parte las constantes, deben ser o bien literales o variables marcadas con la palabra reservada final, el tipo de dato de las constantes debe coincidir con el tipo de dato de la variable, además observe que final hace que la variable se convierta en constante y únicamente pueda recibir valor en el momento de su creación, sin embargo el código no compilara si usa una variable como constante y la misma es un parámetro de una función, así esta última este marcada como final.

2. Control de flujo: ciclos

Los ciclos son otra forma de realizar control de flujo en java al repetir o iterar una serie de isntrucciones.

2.1. Ciclo while

El ciclo más sencillo y fácil de utilizar es el while, el cual repite las instrucciones mientras una condición sea verdadera, en este sentido observe que en sintaxis se parece mucho al condicional if. A continuación se presenta su sintaxis.

while(condición booleana) {
    // Código
}

2.2. Ciclo do while

De forma similar, se encuentra el ciclo do - while el cual es similar al anterior, excepto que este se repite al menos una vez ya que la condición se evalúa al final del ciclo y no al principio. Su sintaxis se muestra a continuación.

do {
    // Código
} while(condición booleana);

2.3. Ciclo for

El tercer ciclo y de hecho uno de los más comunes es el ciclo for el cual en su presentación básica se muestra a continuación

for(inicialización, expresión booleana, actualización) {
    // Código
}

Las tres partes del ciclo for son opcionales y si todas se omiten el ciclo sería un ciclo infinito. También es posible separar múltiples sentencias de inicialización o actualización utilizando comas.

Es importante recordar que la inicialización no puede realizar ocultamiento de variables, es decir, si la variable declarada en la inicialización ya fue declarada antes del ciclo for el compilador arrojará un error indicando que se tiene una doble declaración de la variable, de esta manera las variables declaradas en la inicialización pertenecerán al ámbito del cuerpo del ciclo y no se podrán usar por fuera de este. El último ciclo disponible en Java es el ciclo for - each el cual se muestra a continuación.

for(TipoDato variable : colección) {
    // Código
}

En este caso la colección debe ser una implementación de java.lang.Iterable y el ciclo se ejecuta una vez por cada ítem de la colección, asignando en cada repetición un valor de la colección.

Algo importante a tener en cuenta con los diferentes ciclos es que los bloques de código pueden omitir las llaves cuando solo se trate de una sola línea, en estos casos es importante mantener la tabulación o poner la línea de código al frente de la estructura de control. En cualquier caso esta práctica no es muy recomendable puesto que es más propensa a introducir errores en el código.

3. Etiquetas, continue y break

Las sentencias (if, while, do while, for, switch) de Java pueden ser iniciadas con una etiqueta, normalmente escrita en mayúsculas y terminada en dos puntos. Estas etiquetas sirven para marcar los puntos donde se puede devolver el código mediante una sentencia break o continue, estas dos instrucciones permiten o romper un ciclo con la finalidad de salirse del mismo y no continuar su ejecución o en el caso de continue continuar de inmediato con la siguiente iteración del ciclo sin importar que falten instrucciones por ejecutar. La sintaxis de estas etiquetas es:

break etiquetaOpcional;
continue etiquetaOpcional;

Estas instrucciones están disponibles para los ciclos, adicionalmente las sentencia switch soporta el uso de break. El uso de estas instrucciones es particularmente útil cuando en un ciclo se detecta que no se requiere seguir ejecutando, por ejemplo, si se esta realizando una búsqueda y se encuentra el ítem deseado no se requeriría seguir iterando para buscar, en este caso se puede recurrir a la instrucción break o si la iteración ya no se requiere hacer se usaría continue.. Un ejemplo del uso de etiquetas junto con una sentencia break y continue se muestra a continuación.

PRIMERO: for( ... ) {
    SEGUNDO: for( ... ) {
        if( ... ) {
            // Rompe el ciclo actual terminándolo y haciendo
            // un salto a la etiqueta PRIMERO
            break PRIMERO;
        }
        if( ... ) {
            // Deja de ejecutar la iteración actual del ciclo
            // interno y brinca a la etiqueta SEGUNDO,
            // en este caso no es necesario el uso de etiquetas
            // puesto que la instrucción continue esta en el
            // cuerpo del segundo ciclo
            continue SEGUNDO;
        }
        // ... código ...
    }
}

4. Artículos de Interés

Operadores en Java

Este artículo resume los Operadores en Java y su orden de ejecución como referencia básica para cualquier programador dado que la escritura de expresiones matemáticas es usual y es conveniente tener una referencia a la mano.

1. Precedencia de operadores en Java

En todo lenguaje de programación existe una tabla de precedencia de operadores que indica el orden en el cual se evalúan los mismos, el programador de Java de reconocer y aprender a manejar la tabla de forma que pueda leer y escribir expresiones.

Como nota acerca de la mantenibilidad del software, debe tratarse de escribir expresiones no muy complejas o crípticas puesto que las mismas aunque pueden funcionar dificultan el entendimiento.

1.1. Tabla de Precedencia de Operadores

SímboloOperadorEjemplo
()Paréntesis(i + 2) * y
++, --Operadores unarios posti++, i--
++, --Operadores unarios pre++i, --i
+, -, !Operadores unarios (positivo, negativo, negación)-3, !true
*, /, %Operadores binarios (multiplicación, división, módulo)5 % 2
+, -Operadores binarios (suma y resta)6 - 8
<<, >>, >>>Operadores de desplazamiento (a la izquierda, a la derecha con signo, a la derecha sin signo)2 << 1
<, <=, >, >=, instanceofOperadores relacionales (menor, menor igual, mayor, mayor igual, instanceofobj instanceof String
==, !=Operadores de igualdad (igual, diferente)i == 5
&, ^, |Operadores lógicos (y lógico, or exclusivo lógico, o lógico a nivel de bit)true | false
&&, ||Operadores lógicos booleanos (y en cortocircuito, o en cortocircuitotrue || false
? :Operadores ternarioi > 5 ? "mayor" : "menor"
=, +=, -=, *=, /=, %=, &=, ^=, !=, <<=, >>=, >>>=Operadores de asignacióni += 2;
Tabla 1. Precedencia de Operadores

El operador más prioritario es el paréntesis, es decir, es la primera operación que se realiza, de otra parte las asignaciones son la última operación que se realiza.

Observe que las asignaciones son la única operación que se agrupa de derecha a izquierda, el resto de operadores se evalúa de izquierda a derecha.

1.2. Consideraciones de evaluación de operadores

Bien, ahora consideremos algunos puntos interesantes a tener en mente respecto de los operadores y operandos son:

  • Tenga en cuenta los literales en Java siguen reglas, de manera que debe tener presente en aplicar los operadores en valores correctos, por ejemplo la expresión !0 es un error puesto que el número cero en Java no tiene ninguna relación con el valor booleano falso, como ocurre en otros lenguajes de programación.
  • Los operadores de pre-incremento o pre-decremento se ejecutan al final de la expresión sin embargo no siguen el orden de evaluación tradicional.
  • Los operadores de asignación compuestos como *= solo funcionan si la variable del lado izquierdo ha sido inicializada previamente. El compilador automáticamente realiza un casting de las expresiones que tienen usan operadores de asignación compuestos.
  • Los operadores de comparación son usados con literales y variables nativas en cuyo caso aplican los conceptos de promoción de tipos, también se pueden comparar valores booleanos y finalmente se pueden comparar valores null o String.
Calculadora Operadores
Figura 1. Calculadora HP48

Estas reglas son tan importantes que debes tenerlas en cuenta incluso cuando escribes expresiones matemáticas en Excel o en una calculadora.

2. Promoción y casting de tipos

2.1. Promoción de tipos

La promoción de tipos ocurre para los tipos nativos cuando los mismos o sus literales están presentes en expresiones, las siguientes cuatro son las reglas que debe considerar respecto a la promoción de tipos.

  1. Si dos operandos tienen diferentes tipos, Java realiza una promoción automática del tipo más pequeño al más grande.
  2. Si uno de los operandos es un entero y el otro es de punto flotante, Java promueve el entero a punto flotante.
  3. Los operandos más pequeños que un int son promovidos primero a int antes de ser evaluados en la expresión incluso si ninguno de los dos operandos es int. Esta regla excluye los operadores unarios.
  4. Después de las promociones todos los operandos tienen el mismo tipo de dato y a causa de esto el resultado tendrá el mismo tipo de dato que los operandos.

Las valores pueden ser sujetos a conversiones de tipo forzadas, estas conversiones pueden eventualmente conllevar perdida de datos, sin embargo es una forma de decirle al compilador que sabemos que estamos haciendo.

2.2. Casting o conversión de tipos

Los casting se realizan con el uso de paréntesis y entre ellos el tipo de dato al cual se quiere hacer la conversión, en el siguiente ejemplo se muestra un casting de una expresión que de otra manera arrojaría error, note además como se esta aplicando la tercera regla de promoción en este ejemplo.

public static void main(String... args) {
    short a = 3;
    short b = 4;
    short c = (short) (a * b);
}

En las ocasiones en las que existan perdidas de datos puede ser o bien por que se trunca la parte decimal de un número en punto flotante o por que se desborda un número, el desborde es conocido como overflow o underflow y ocurre cuando un valor supera el máximo que puede ser representado por su tipo, por ejemplo los byte pueden almacenar desde -128 hasta 127, si una variable de este tipo se le asigna el número 127 y a continuación se le suma 3, no podrá almacenar el número 130 ya que este último valor no cabe en un byte, en su lugar hay un desbordamiento y el valor resultante de esta operación termina siendo -126.

3. Operadores relacionales, lógicos y en cortocircuito en Java

Las operaciones relacionales permiten comparar cantidades como son: mayor, mayor que, menor, menor que, igual y diferente. Estos operadores funcionan a nivel de tipos nativos por lo cual es importante no utilizarlos por ejemplo para comparar referencias objetuales puesto que la operación si bien puede arrojar resultados correctos puede conllevar a resultados inesperados.

3.1. Operadores lógicos y tablas de verdad

Los operadores lógicos corresponden a las siguientes tablas de verdad .Como punto curioso es conveniente observar que mientras que los operadores aritméticos son cerrados, es decir, considere la suma 3+5 el operador suma (llamado binario porque opera sobre dos argumentos) toma dos números y arroja como respuesta otro número, por ello se llama cerrado, pero una operación como 5<3 si opera sobre números y su resultado es un valor booleano.

Las operaciones relacionales habitualmente se usan en conjunto con operadores lógicos y las diferentes combinaciones de valores y resultados de las operaciones lógicas se muestran a continuación.

pqp & q
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse
Tabla 2. Operación Y
pqp | q
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse
Tabla 3. Operación O
pqp ^ q
truetruefalse
truefalsetrue
falsetruetrue
falsefalsefalse
Tabla 4. Operación XOR

Un concepto no tan conocido pero importante es la diferencia técnica que existe entre el operadores “&” y “&&” igualmente con la “O” lógica.

Cuando se usan dos operadores seguidos se llama operación en cortocircuito, para el caso de la “&&” se evalúa el primer argumento si el mismo es falso no se evalúa el segundo puesto que la operación ya tendrá como resultado falso, para corrobóralo verifique nuevamente la tabla de verdad del operación &, ahora con la operación O funciona de forma similar excepto que se omite la evaluación del segundo argumento si el primero es verdadero, ahora bien existe la versión sin el cortocircuito porque en ocasiones queremos forzar la ejecución de la evaluación de ambas partes especialmente si estas contienen operadores que modifican los valores de las variables como los de pre-incremento o post-incremento.

3.2. Ejemplos de pre-incremento y post-incremento

Para efectos de comprender mejor los operadores de post-incremento y pre-decremento analice la siguiente sentencia e infiera cual será el resultado de la operación, luego mire el uso de la operación y lógica y su modificación de la variable.

int i = 10;
i = i++ + ++i;
System.out.println(i);
if(5 < 3 && i++ > 5) {}
System.out.println(i);
if(5 < 3 & i++ > 5) {}
System.out.println(i);

Finalmente es conveniente anotar que estas operaciones son muy utilizadas especialmente cuando se hace uso de verificación de null antes de proceder con la verificación de una condición como se muestra en el siguiente ejemplo, el cual evita que se produzca el famoso error NullPointerException puesto que antes de avaluar esa operación se verifica si la referencia es nula.

if(str != null && str.length < 10)...

4. Artículos de Interés

Clases Métodos Atributos y Código básico en Java

Programar en el lenguaje Java requiere la comprensión del desarrollo Orientado a Objetos, el manejo de referencias y el modelo de memoria del computador. Este artículo recopila los detalles para escribir Clases Métodos Atributos y Código básico en Java.

1. Clases en Java

Las clases son las piezas fundamentales de programación, aunque es posible aprender a programar en Java y sin adentrarse en los detalles de la Programación Orientada a Objetos.

En la Figura 1, se muestra la diferencia existente entre las aproximaciones a la programación siendo los lenguajes de bajo nivel aquellos que se encuentran más cerca de la máquina y los de alto nivel aquellos que se encuentran cerca del humano (programador).

1.1. Atributos y Métodos

Los atributos y métodos se conocen como miembros y componen las clases en Java.

  • Atributos: también llamados propiedades, variables o campos están relacionados con el estado y los datos que se podrán almacenar.
  • Métodos: también llamados operaciones, procedimientos o funciones están relacionados con las acciones que se podrán ejecutar, aquí se encuentra la lógica.
Lenguajes de Programación Código Java
Figura 1. Lenguajes de alto y bajo nivel

Las clases son simplemente plantillas que ayudan a definir características y normalmente están relacionadas con conceptos concretos o abstractos del contexto que se programa, por ejemplo: Casa, Vehículo, Nota Musical.

En Java una clase se crea típicamente en un archivo, sin embargo, es posible tener varias clases en un mismo archivo, de cualquier manera un archivo solo puede tener la definición de una clase pública (conceptos como público, privado y otros se conocen como modificadores de acceso y se verán más adelante).

Es necesario para Java que el nombre de la clase pública coincida con el nombre del archivo. A continuación se muestra un ejemplo de clase:

public class Person {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

1.2. Anatomía de una Clase en Java

Esta clase inicia con la palabra reservada public la cual indica que esta clase puede ser usada por otras clases, además tiene un atributo denominado name el cual almacena información del nombre de la persona como una secuencia de caracteres o String y al ser privado este atributo solamente puede ser usado al interior de la clase Person, finalmente tiene dos métodos públicos que permiten acceder al atributo privado, de hecho estos métodos son muy conocidos y se llaman métodos getter and setter y permiten que los objetos de la clase accedan leyendo o escribiendo respectivamente a los atributos privados.

1.3. Métodos get y set: encapsulamiento

Es muy común no permitir el acceso directo de los objetos a los atributos ya que a menudo se quiere implementar una lógica antes de escribir o leer que permita hacer validaciones y transformaciones, por ejemplo si la clase fuera Hora podríamos pensar en almacenarla como la cantidad de milisegundos que han transcurrido desde media noche, en este sentido solamente tendríamos un atributo, pero los métodos set y get podrían establecer y devolver la hora usando un String que la represente. El uso de los métodos getter y setter se conoce como encapsulamiento.

Cuando construimos clases en Java debemos crear un archivo por cada clase, es obligatorio que el nombre del archivo coincida con el nombre de la clase marcada como pública, en este sentido en un mismo archivo pueden existir más de una clase, pero solamente una de ellas puede ser marcada como pública.

Revisa más detalles de encapsulamiento aquí.

2. Objetos en Java

Los objetos por su parte, son instancias de las clases, es decir, podemos asimilar la clase como un molde y los objetos son aquellas creaciones que salen del molde, por lo tanto cada objeto tiene su propia copia de los atributos y aunque los objetos de una misma clase tengan los mismos atributos podrán diferir en sus valores de forma independiente. Crear un objeto también se conoce como instanciar una clase y para ello se usa la palabra reservada new como se muestra a continuación en el listado de código 2.

Person p1 = new Person()

Es importante reconocer la palabra reservada new la cual instancia la clase y crea un nuevo objeto en memoria con su propia copia de las variables o atributos de la clase.

El acceso a los miembros del objeto se realiza utilizando el operador punto (.) de esta manera y basados en los modificadores de acceso un objeto puede utilizar sus atributos y métodos.

3. Métodos en Java

Los métodos y tomando nuestra clase de ejemplo Person son piezas reutilizables de lógica que están programados usando programación estructurada y los cuales en su primera línea (también llamada firma o prototipo) especifican el nivel de acceso, en el ejemplo del código 1 los métodos son públicos, indicando que estos métodos se pueden llamar desde cualquier objeto, continuando con el tipo de retorno, es decir, el tipo de dato que es devuelto a quien lo llame, a continuación un identificador, es decir, un nombre que se le da al método y finalizando con una lista de parámetros separados por coma, opcionalmente los métodos terminan con la especificación de los tipos de errores que pueden lanzar.

3.1. Tipo de Retorno en los métodos

El tipo de retorno y los parámetros o en otras palabras, la salida del método y las entradas del método, siendo un caso particular de lo anterior cuando un método no devuelve nada en cuyo caso se utiliza la palabra reservada void, tal como se ha hecho en el método setName. Como nota al margen, se debe considerar que Java es un lenguaje fuertemente tipado, lo que significa que Java verifica que los tipos de datos de las expresiones de retorno o de las expresiones de llamados a métodos que asignan valores a parámetros coincidan, en nuestro ejemplo podemos ver como el tipo de retorno del método getName() es el mismo que la expresión return que en este caso es String.

4. Modificadores de acceso de Métodos

Los modificadores de acceso permiten establecer el nivel de visibilidad de una clase, método o atributo en Java, este nivel de visibilidad esta relacionado con los lugares del código donde los podemos referir, esto es lo que se conoce como nivel de exposición a posibles llamadores y por lo general hay que tratar de mantener este nivel de exposición al mínimo posible utilizando lo que conocemos como principio del menor privilegio, es mejor quitar todos los permisos e ir agregando a medida que se necesitan.

4.1. Niveles de acceso de los métodos

Java posee cuatro niveles de acceso que son los siguientes y que se describen del más restrictivo al menos restrictivo.

4.2. Privado

Se utiliza con la palabra reservada private, e indica que los atributos o métodos solamente pueden ser llamados en la propia clase donde se definen.

4.3. Protegido

Se utiliza con la palabra reservada protected, e indica que los atributos o métodos solamente pueden ser llamados en la propia clase donde se definen o en clases derivadas (haciendo uso de herencia).

4.4. Por defecto

No se requiere especificar ninguna palabra clave, solo basta con definir la clase, método o atributo y significa que solamente las propia clase o clases del mismo paquete podrán acceder a dichos miembros.

4.5. Público

Es el acceso más permisivo de todos y para ello se utiliza la palabra reservada public que indica que cualquier objeto en cualquier clase puede acceder a las clases, métodos o atributos etiquetadas con este modificador de acceso.

5. Comentarios en Java

En todo lenguaje de programación es importante realizar comentarios a fin de permitir documentar el código, estas es una de las prácticas más convenientes y que todo buen programador tiene en cuenta.

/*
* Este es un comentario de varias líneas
*/
// Este es un comentario de una línea
/**
* Este es un comentario con propósitos de documentación
*/

Los comentarios son instrucciones no ejecutable que es usado para clarificar el código. Los tipos de comentarios son los siguientes.

5.1. Comentario de documentación

Utilizan etiquetas especiales para que la utilidad javadoc genere automáticamente archivos HTML que son un compendio de la documentación, estos comentarios se utilizan especialmente con las clases, métodos y atributos.

5.2. Comentarios una o varias líneas

Los comentarios de una línea comentan todo lo que existe a la derecha, por lo que en ocasiones se ponen al final de declaraciones de variables, en general, la practica común utilizada es dejar los comentarios en líneas independientes.

6. Método main Código Java

Los programas de Java son ejecutados usando un interprete de instrucciones conocido como Java Virtual Machine (JVM), es decir, no es directamente el sistema operativo y el procesador. La JVM busca las instrucciones iniciales a ejecutar en el método especial denominado main. En general, el método main se escribe como se muestra a continuación.

public static void main(String args[]) {
}

La declaración de este método tiene algunas partes importantes a considerar.

  1. Es público, debido a que el método será invocado por la JVM y por lo tanto debe ser visible desde afuera de la clase donde fue declarado.
  2. Es estático, para ser invocado sin necesidad de instanciar la clase (crear un objeto) donde se encuentre.
  3. No retorna nada debido a que la JVM no espera ninguna respuesta.
  4. Recibe una lista de parámetros en cadenas de texto o String que son los parámetros de la línea de comandos cuando se ejecuta desde una consola, esta lista de parámetros es un arreglo nativo que puede ser escrito alternativamente como String [] args o también String... args. Este último caso se conoce como parámetros variables y simplifica las labores de creación de un arreglo al momento de invocar un método que recibe varios parámetros del mismo tipo.

6.1. Ejecución del main de Java

Si se quiere ejecutar un archivo .class (el resultante de compilar el archivo .java) el mismo deberá tener al menos un método main, para tal efecto se pueden usar los comandos javac Clase.class y java Clase arg1 arg2 respectivamente para compilar y ejecutar desde la línea de comandos.

Para más detalles de ejecución de programas Java sigue este link.

7. Identificadores en Java

Los identificadores son nombres que se ponen a los diferentes elementos del código, son elegidos por el programador y normalmente siguen un estándar, el más usado en Java es Camel Case que consiste en crear los nombres en minúsculas excepto por la primera letra de cada palabra, ayudando a la legibilidad.

Los nombre seleccionados deben ser reflexionados y puestos con calma, escogerlos bien ayuda al mantenimiento y entendimiento del código. Para el caso de paquetes, métodos, atributos, variables se inicia con minúsculas, mientras que las clases y las interfaces comienzan con mayúscula. Todos los identificadores siguen las siguientes reglas:

  • El nombre debe empezar con letra o símbolo $ o _.
  • Caracteres subsecuentes pueden ser números.
  • No pueden ser palabras reservadas.

8. Palabras reservadas en Java

Las palabras reservadas se utilizan para fines particulares del lenguaje y no pueden ser elegidas o cambiadas a justo del programados. Así mismo, las palabras clave no pueden ser usadas por el programador como identificadores, a continuación se muestra una tabla con las diferentes palabras clave.

abstract assert boolean break byte case catch char class const continue default do double else eunum extend false final finally float for goto if implements import instanceof int interface long native new null package private protected public return short static stricttfp super switch synchronized this throw throws transient true try void volatile while

9. Artículos de Interés

Link con la especificaciones de Java Formales de Oracle

Historia Funcionamiento y Características de Java

1. ¿Qué es Java?

Este artículo te explica cual es el funcionamiento java, pero también te muestra la historia y la evolución del lenguaje. También entenderás las características que han hecho de este lenguaje de programación uno de los más importantes del mundo.

Como Lenguaje de Programación Java permite el desarrollo de software para diferentes tipos de aplicaciones, incluyendo:

  • Aplicaciones web.
  • Aplicaciones empresariales.
  • Aplicaciones de escritorio.
  • Aplicaciones cliente servidor.
  • Aplicaciones para dispositivos móviles.
  • Aplicaciones para dispositivos de electrónica de consumo como neveras o televisores.

2. Filosofía y características de Java

Java ha sido diseñado con varias características en mente, entre ellas se pueden encontrar las siguientes.

2.1. Orientación a objetos

Java fue creado usando Programación Orientada a Objetos y en ese sentido incorpora en sus pilares constructivos todos los conceptos de este paradigma de programación.

Java dispone de algunas características nativas que no incluyen objetos, concretamente los tipos nativos que permiten mayor eficiencia cuando el programador lo requiera.

2.2. Portabilidad

Quizás esta sea una de las características más relevantes e importantes de Java y de hecho nació con ella.
La portabilidad le permite a Java que los programas puedan ejecutarse en cualquier plataforma sin necesidad de volver a compilar el código.

Portabilidad entre diferentes sistemas operativos
Figura 1. Portabilidad

Esto ha permitido a Java acoplarse exitosamente al mundo de Internet donde concurren una gran cantidad de dispositivos, arquitecturas de computadores y sistemas operativos, esto le abrió paso rápidamente en sus inicios y lo convirtió en uno de los lenguajes más populares del mundo. Este concepto es conocido como WORA (Write Once, Run Anywhere).

2.3. Simple

Java es un lenguaje ampliamente utilizado no se logra si no se cuenta con una sencillez y simplicidad que le permita a las personas programar en el lenguaje y esto ya que adoptó los paradigmas más exitosos como POO.

Java eliminó la herencia múltiple y automatizó la liberación de la memoria, este último caso dificultaba especialmente la programación puesto que si la labor de eliminar la memoria manualmente es responsabilidad del programador, es común que se elimine objetos de la memoria en el momento inadecuado, es decir, cuando todavía se necesitan.

2.4. Gran cantidad de librerías

Existe una comunidad muy grande de programadores (del orden de millones) hay muchos aportes, y en términos de programación eso significa disponer de mucho código desarrollado y agrupado en librerías para realizar todo tipo de tareas, además estas librerías por lo general son abiertas o gratuitas.

2.5. Moderno y futurista

Java recibe constantemente actualizaciones que normalmente amplían el lenguaje e incluyen características importantes y que facilitan las operaciones, lo cual facilita al programador una mayor capacidad de expresión de manera que con menos código se puede hacer más, haciéndolo más simple y más fácil de mantener.

2.5. Maduro

Nació en 1996 luego de más de dos décadas de desarrollo y a la fecha después de una gran cantidad de actualizaciones ha logrado la madurez estabilidad que lo hace apto incluso para aplicaciones empresariales de misión crítica.

2.6. Fuertemente tipado

Esta característica de Java es quizás una de las dificultades de los programadores que inician con el lenguaje, pero una vez dominado este concepto el programador encontrará que es un beneficio muy grande ya que evita errores en tiempo de ejecución.

También sucede que algunos programadores acostumbrados a lenguajes con un tipado débil como Visual Basic o JavaScript encuentran retador trabajar en Java. De cualquiera manera el programador que inicia en Java rápidamente se acostumbra a trabajar con los tipos de forma obligatoria.

3. Historia de Java

3.1. Comienzos

Java comenzó como un proyecto en 1991 desarrollado de la compañía Sun Microsystems, el proyecto a cargo de James Gosling se llamo originalmente Oak, este nombre ya estaba registrado como marca, por lo cual se optó por el nombre Green, debido al nombre del proyecto principal donde se desarrollaba y finalmente paso a denominarse Java.

El origen del nombre Java no esta claro, una de las teorías más aceptadas es debido a un tipo de café que se vendía cerca de las oficinas del proyecto. De aquí, el hecho que uno de los iconos más representativos de Java es una taza de café.

Logo de Java
Figura 2. Icono de Java

El lenguaje nació con la idea de escribir una sola vez y ejecutar en cualquier lugar, este principio, se conoce como WORA (Write Once, Run Anywhere) un hecho que es reforzado en el pilar de la portabilidad.

Java entonces se orientó desde su nacimiento a la Web, en ese entonces era el año de 1994 y la Web aun era muy incipiente, sin embargo los visionarios de Java hicieron una versión de navegador Web propia y soportada por Java, para 1995 se podía descargar HotJava y casi que paralelamente uno de los navegadores más populares del momento, Netscape, incorporaba soporte nativo para Java.

3.2. Evolución

En esa época, Java se ejecutaba como pequeñas aplicaciones dentro de las páginas web que se llamaban applets, y para entonces las páginas web contenían casi que exclusivamente texto e imágenes, la posibilidad de incorporar contenido dinámico ejecutado en el navegador (cliente) fue toda una revolución ya que abría la puerta a un sin fin de aplicaciones, de hecho, incluso en las empresas se comenzó a dar un paso de sistemas cliente – servidor a sistemas cliente – servidor pero habilitados en la web (Web Enabled). Java tenía entonces todo el camino y potencial para desarrollarse y así fue.

De allí en adelante, y luego del primer lanzamiento de la versión de Java JDK 1.0 en 1996 seguirían una serie de evoluciones de las cuales algunas de las más relevantes incluye la versión JDK 1.4 en 2002, JDK 5 en 2004 y la versión JDK 8 en 2014. Cada nueva versión incorpora una adición importante de paquetes a la librería estándar de Java y una extensión de capacidades del lenguaje.

3.3. Actualidad

La empresa creadora de Java Sun Microsystems fue adquirida por el Oracle y en esta operación fue absorbido Java, para fortuna de los desarrolladores Oracle abrió el código fuente de Java convirtiendo el núcleo del lenguaje en OpenSource.

Como consecuencia algunas compañías construyeran sus propias versiones de Java, de manera que se encuentran implementaciones de Java de Oracle, de IBM y de algunas iniciativas del mundo OpenSource.

4. Funcionamiento de Java

4.1. Generalidades del Funcionamiento de Java

El funcionamiento de Java se basa en el hecho que exista una máquina virtual, esto es indispensable para lograr la portabilidad, a su vez el hecho de tener un lenguaje que se interpreta en una máquina virtual en lugar de hacerlo directamente en el procesador (como ocurría con C/C++) ocasionaba que los programas inicialmente desarrollados en Java no fueran tan rápidos.

Un programa en Java funciona entonces con la codificación en un archivo de texto cuya extensión es .java este archivo es compilado a un lenguaje denominado bytecode y el resultado de dicha compilación es un archivo .class este último archivo es susceptible de ser ejecutado por la máquina virtual que a su vez es ejecutada por el procesador real.

La pregunta es para que usar una máquina virtual en lugar de ejecutar directamente en el procesador real, y la respuesta subyace en la característica de Java de la portabilidad que permite ejecutar el código en cualquier arquitectura o sistema operativo.

Debido a que el bytecodes es ejecutado por la máquina virtual y que existen diferentes máquinas virtuales adaptadas a cada sistema operativo o arquitectura de computadora, pero aunque existan varias máquinas virtuales todas ellas son capaces de ejecutar el mismo bytecode, allí radica el éxito de la portabilidad de Java.

4.2. Definiciones en el uso de Java

A continuación se proporciona algunas de las definiciones más importantes que debe tener en cuenta a la hora de comprender como funciona Java

  • JVM: esta sigla hace referencia a la Java Virtual Machine, es e software encargado de ejecutar las instrucciones en bytecodes en el procesador real.
  • JRE: Java Runtime Enviroment es el componente de Java que contiene la JVM y demás utilidades y permite ejecutar programas en Java. Toda persona que desee correr programas hechos en Java debe disponer de una versión del JRE instalada en su computador.
  • JDK: Java Development Kit es un conjunto de componentes de software que incluyen el JRE y además los ejecutables necesarios para desarrollar y depurar código.

Veamos lo anterior en un poco más de detalle y haciendo referencia a la Figura 2. La imagen clarifica como se da el proceso de codificación y ejecución de un programa en Java y para leerla debemos comenzar por la izquierda donde se muestra el código fuente, este código es el que produce un programador y lo hace adhiriéndose a la sintaxis de Java. el código no es más que un conjunto de archivos .java agrupados en diferentes carpetas.

4.3. Bytecodes de Java

El código escrito en Java es compilado (traducido) al lenguaje denominado Bytecodes que puede entender la JVM y ésta es la encargada de ejecutar las instrucciones (en un proceso de interpretación) en la máquina o procesador físico real.

El código Java puede correr en diferentes versiones de la JVM una por cada sistema operativo y arquitectura de máquina real, su código y en general siempre funcionará en la JVM quedando independiente de la máquina real logrando así el propósito de Java de la portabilidad.

4.4. JDK y proceso de compilación

Los componentes principales para desarrollar y que están incluidos en el JDK incluyen los ejecutables java para ejecutar archivos .class o .jar y otras utilidades como javac que realiza la compilación de java a bytecodes, estas son las dos utilidades principales del JDK pero no son las únicas existen muchas otras que ayudan a depurar y encontrar errores entre otras.

Proceso de Compilación
Figura 2. Proceso de codificación y ejecución en Java

Una claridad importante en el proceso de compilación y ejecución de Java y que he dejado por alto es la diferencia existente entre los archivos class y jar y la diferencia realmente es muy simple, siendo el primer tipo de archivo el resultado de la compilación de un solo archivo de Java a bytecodes y el segundo tipo, es decir, el jar es un archivo especial que contiene al interior muchos class. Lo más normal en un programa Java es que existan muchos archivos Java y por lo tanto resulten del proceso de compilación muchos archivos class por lo tanto lo más habitual es tener empaquetados todos esos archivos en un solo jar. De hecho las librerías construidas con finalidades especificas son distribuidas en formato jar.

5. Descarga java

En el siguiente enlace podrás descargar Java, ten en cuenta que debes tener una cuenta en Oracle la cual puedes crear totalmente gratis.

https://www.oracle.com/java/technologies/downloads/

6. Artículos de Interés

Entradas recientes »

© 2024 ochoscar's blog

Tema por Anders NorenArriba ↑