🎃 Grandes descuentos en libros en línea, eformaciones y vídeos*. Código CALABAZA30. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. Java y Eclipse
  3. La caja de herramientas de Java
Extrait - Java y Eclipse Desarrolle una aplicación con Java y Eclipse (2a edición)
Extractos del libro
Java y Eclipse Desarrolle una aplicación con Java y Eclipse (2a edición)
1 opinión
Volver a la página de compra del libro

La caja de herramientas de Java

Genéricos

Java introdujo otro tipo de polimorfismo desde Java 1.5, llamado paramétrico, que permite tipar y restringir las clases. Estas restricciones se expresan mediante un notación entre los símbolos < y >.

Retomando el ejemplo de los animales, y suponiendo que la aplicación concierne a un zoo, la modelización incluirá corrales que agrupan diferentes animales. En estos corrales, se debe evitar a toda costa mezclar carnívoros con herbívoros. Los genéricos permiten establecer este tipo de seguridad.

Ejemplo:

public class Corral<T extends Animal> {   
   private T[] animales;  
   ...    
   public anadir(T animal) {  
         ...  
   }   
} 

El ejemplo anterior define una noción de corral para cualquier tipo de animales, gracias a la notación entre los símbolos < y >. Esta notación define un tipo particular, con el alias T, que se usará para todos los objetos que extienden la clase Animal. Se puede entonces usar este alias en el interior de la clase en substitución del tipo.

La clase Corral puede después instanciarse con el operador diamante desde Java 1.7 como sigue:

Ejemplo de utilización con Java 1.9:

Corral<Tigre> corralDeTigres = new Corral<>(); 

O en Java 1.5 como:

Ejemplo...

Colecciones

Cuando se modeliza un programa  con la ayuda de clases, es necesario crear abstracciones de datos. Sin embargo, igual de importante es poder utilizar esos datos como conjunto.

Es posible utilizar vectores para ello, como:

Animal[] animales = new Animal[42]; 

Pero los vectores no son muy flexibles: no se pueden redimensionar (se dice que tienen un tamaño fijo), no tienen métodos, etc. Para aprovechar todas estas características, una colección es una herramienta muy potente.

El concepto de colección es simple. Corresponde a las colecciones de sellos, conchas, etc. Los objetos de la misma naturaleza se almacenan en el interior de una colección. Java creó desde su primera versión en el package java.util clases e interfaces que dan una ayuda para modelizar estos conjuntos de datos, y pone a disposición del programador varios tipos de colecciones que difieren en sus características: ordenadas o no, datos repetidos o no, valores null aceptados o no, ordenación por defecto o no, etc. Todas tienen la capacidad de redimensionarse automáticamente al añadir o suprimir objetos.

Una instancia de una colección es un objeto como toda instancia de una clase.

Java define varias interfaces que permiten especificar el contrato y el comportamiento de estos conjuntos de datos. Contamos muchos, pero tradicionalmente existen dos que sirven de base:

  • La interfaz Map representa el contrato...

Gestión de los errores

Lo cotidiano para un programa y sus desarrolladores es gestionar los errores. Incluso es correcto decir que en un contexto industrial, la gestión de los errores representa más tiempo y código que el funcionamiento normal.

Por lo tanto debe en todos los programas aprender a gestionar los datos incorrectos introducidos por un usuario (introduce letras en vez de números para un precio), a gestionar los errores técnicos (la conexión a la base de datos está desconectada o la red se ha caído), a crear estrategias de compensación (reintentar una connexión al menos tres veces antes de abandonar, etc.).

Para ganar en claridad, Java proporciona un mecanismo de gestión de los errores por excepción. Se trata de ejecutar una parte del código susceptible de generar excepciones (por lo tanto errores) englobándola y proporcionar instrucciones alternativas si se produce alguna excepción. Una conexión se puede entonces reintentar, se pueden indicar valores por defecto...

Una excepción es por lo tanto un evento que interrumpe la secuencia normal de la aplicación y que permite ejecutar un código alternativo.

Esto se hace con la ayuda de bloques try-catch-finally, como en el siguiente ejemplo:

try {   
   // acceder a la red  
   // tratar los datos recuperados  
} catch (IOException...

Boxing/Unboxing

Dentro de Java, todo son objetos... o casi: existe lo que se llaman los tipos primitivos, como int, double, float, char, boolean.

Cada uno de estos tipos primitivos tiene su equivalente en objeto: Integer, Double, Float, Character, Boolean.

Para facilitar la codificación, el lenguaje Java puede realizar una conversión automática del tipo primitivo a su equivalente en objeto: se trata del mecanismo de boxing.

private Integer transformar(int entero) {   
   return entero;      // convierte el tipo primitivo int  
                       // en objeto de tipo Integer   
} 

Para hacerlo explícitamente, utilice el método estático valueOf() de las clases Boolean, Integer, Double, Float o Character.

private Boolean transformar(boolean booleano) {   
   // convierte el tipo primitivo boolean  
   // en objeto de tipo Boolean  
   return Boolean.valueOf(booleano);  
} 

La operación inversa, llamada unboxing, permite por su parte transformar objetos Java en su tipo primitivo.

private double convertir(Double decimal) {   
   return decimal;    // convierte el objeto de tipo Double  
                      // en valor de tipo...

Enums

Un enum es un tipo especial de datos que proporciona un juego de constantes predefinidas

Es perfectamente posible crear un enum personalizado: se define como una clase o una interfaz en su propio archivo, con la palabra clave enum.

Por ejemplo:

public enum Direccion {   
   NORTE, SUR, ESTE, OESTE  
} 

Puede también darle atributos, métodos y un constructor. La restricción principal es entonces que el constructor debe ser privado o pertenecer a un package privado: no es posible crear un valor enum directamente, estos valores solo pueden ser los declarados en el enum. Esta restricción prohibe también cualquier herencia de enum.

public enum Direccion {   
   NORTE("septentrional"),  
   SUR("sur"),  
   ESTE("levante"),  
   OESTE("poniente"); // el punto y coma es ahora necesario  
                      // ya que existen miembros en el enum  
   private String antiguoNombre;  
  
   private Direccion(String nombre) {  
         this.antiguoNombre = nombre;  
   }  
     
//    public Direccion(String nombre) { // no compilará  
//  ...

Gestión del tiempo y de las fechas

La gestión de fechas ha sido históricamente un punto débil de Java.

Antes de la aparición de Java 8, crear una fecha, es decir un instante preciso de una línea temporal, se hacía con la ayuda de la clase java.util.Date. Por ejemplo:

Date ahora = new Date(); 

El problema era ante todo conceptual: un objeto Date representa realmente un instante con una precisión de milisegundos, podríamos decir un tiempo máquina, calculado por el número de milisegundos transcurridos desde el epoch Java: el 1 de Enero de 1970 a la medianoche UTC (Coordinated Universal Time). Sin embargo, si utilizamos el método toString() del objeto Date:

String descripcion = new Date().toString();  
System.out.println(descripcion); // Fri Jun 01 14:09:38 CEST 2018 

obtenemos una descripción de la fecha realizada para ser leída por humanos, sobre todo por la presencia del huso horario (CEST significa Central European Summer Time, o sea la hora de verano en el huso horario de Madrid).

Estas dos nociones de un tiempo máquina y de un tiempo humano son conceptualmente diferentes, ya que un tiempo humano utiliza siempre el concepto de huso horario y de horario de verano/invierno mientras que un tiempo máquina es esencialmente un sello de tiempo (un timestamp en inglés). De la misma manera, un niño nacido en Madrid el 1 de Julio de 2018 a las 15h55 y un niño nacido el mismo día a la misma hora y mismo minuto en Katmandú no tienen exactamente la misma edad: tienen algunas horas de diferencia (3 horas y 45 minutos para ser precisos).

La clase Date era tan errónea que casi todos sus métodos se han marcado como deprecados en la siguiente versión de Java, con la introducción de una clase Calendar, que también sufría de problemas conceptuales.

Para obtener el año desde un objeto Date, hay que añadirle 1900, ¡y el mes 0 es el mes de Enero!

Java, desde la versión 8, introduce una revisión completa de la gestión del tiempo, y proporciona las clases del package java.time. Sus principales clases son: Instant, LocalDate, LocalTime, LocalDateTime, ZoneId y ZonedDateTime. Las clases Date y Calendar son también compatibles con estas nuevas clases.

Si crea un nuevo programa con Java 9, priorice siempre las clases...

Eventos

Toda aplicación que incluya interfaces gráficas debe ser capaz de gestionar eventos: se trata de un concepto esencial de las interfaces hombre-máquina (o HMI).

Un evento es una señal que se envía desde las capas base del sistema operativo o de Java para notificar al programa de que algo notable acaba de suceder: el usuario ha hecho clic sobre un botón, teclea con el teclado, pasa por encima de una determinada zona de la interfaz gráfica con el ratón,...

Desde el punto de vista del desarrollador, los componentes gráficos generarán eventos de varios tipos, y tendrá que programar secuencias de código para reaccionar a la aparición de estos eventos.

Existen realmente muchos tipos de eventos. Aquí tiene, por ejemplo, una parte de los que están disponibles para un simple componente JButton (un botón en el que se puede hacer clic).

images/04-01.png

Solo para el ratón, existen cinco eventos posibles de base, más dos para los desplazamientos del ratón, y uno para la ruedecilla.

Para programar acciones en respuesta a estos eventos, debe añadir lo que se llama un escuchador de eventos, un listener en terminología Java.

Como recordatorio, en el capítulo Toma de contacto de Eclipse, codificó lo siguiente para responder al clic del botón:

JButton btnValidar = new Jbutton("Validar");  
btnValidar.addActionListener(new...

Lambdas

Las expresiones lambda son otra de las novedades de Java desde la versión 8.

En una aplicación, particularmente en su parte gráfica, es muy habitual programar pequeñas clases llamadas internas anónimas (ya que son tan pequeñas que no tienen nombre, y están codificadas dentro de una clase).

Retomando el ejemplo del escuchador de eventos:

JButton btnValidar = new Jbutton("Validar");  
btnValidar.addActionListener(new ActionListener() {  
   public void actionPerformed(ActionEvent e) {  
         System.out.println("¡Acción!");  
   }  
}); 

En realidad creó una clase anónima que hereda de la interfaz ActionListener y codificó la acción resultante del clic en el botón dentro del método actionPerformed.

Esta interfaz ActionListener es muy sencilla: solo tiene un método que implementar. Es lo que llamamos una interfaz funcional.

La escritura puede entonces simplificarse gracias a las expresiones lambda de la siguiente manera:

    btnValidar.addActionListener(  
         (ActionEvent e) -> {   
                System.out.println("¡Acción!");   
   ...

Streams

Es bastante habitual trabajar con conjuntos de datos cuando se codifica una aplicación. Un ejemplo característico es recibir una lista de objetos, recorrer esta lista y realizar tratamientos para cada uno de los objetos incluidos en ella.

Esto se traduce, habitualmente, en un bucle for (o un bucle while):

List<String> valores = Arrays.asList("a", "b", "c", "b1", "c1"); 
for (String valor: valores) {  
    procesamiento(valor);  
}  
Iterator<Integer> iterator = valors.iterator();  
while (iterator.hasNext()) {  
    String valor = iterator.next();  
    procesamiento(valor);  
} 

procesamiento(valor) es la llamada al método donde se realiza el procesamiento.

Desde la versión 8, Java permite simplificar esta escritura mediante el uso de streams.

Un stream, en Java, es una unidad. Sin entrar en la definición matemática formal, se trata de una secuencia de elementos desde una fuente, que permite realizar operaciones de forma encadenada.

En particular, escribir el siguiente código:

valors.stream()  
    .filter(val -> val.startsWith("c"))  
    .map(String::toUpperCase)  
    .distinct()  
    .sorted()  ...

Optional

La clase Optional existe desde Java 8. Se trata de un contenedor que contiene o no un valor de cualquier tipo.

Imaginemos el siguiente caso. Un ordenador tiene una tarjeta de sonido. Esta tarjeta está presente en la mayoría de los casos, pero no necesariamente siempre.

Una posible modelización de esta característica sería crear un miembro de clase, que valiese null cuando no hubiera tarjeta de sonido, y con un valor cuando la tarjeta de sonido sí estuviera presente.

Esto supone tener que comprobar constantemente, en el código, el valor del miembro, pues si el código realiza una operación como:

laTarjeta.getVolumen().aumentar(); 

en el caso de una tarjeta ausente, producirá un error NullPointerException en tiempo de ejecución.

Una manera de proteger el código es modificarlo así:

if (laTarjeta!= null) {  
    Volumen volumen = laTarjeta.getVolumen();  
    if (volumen!= null) {  
        volumen.aumentar();  
    }  
} 

Esto es posible, aunque tedioso...

La clase Optional permite resolver de una manera más elegante este problema:

Optional.ofNullable(laTarjeta)  
    .map(tarjeta -> tarjeta.getVolumen())  
    .ifPresent(volumen -> volumen.aumentar()) 

Este código crea un Optional...

Clases gráficas

Java proporciona varios componentes gráficos básicos, gracias a los packages java.awt y javax.swing. Los componentes AWT son históricamente los primeros en aparecer y han sido enriquecidos más tarde con los componentes Swing.

De una manera más general, utilice los componentes Swing. Disponen de más posibilidades de interacción y de riqueza gráfica.

Explicamos estos componentes en su versión por defecto: tal vez la apariencia gráfica no es la más agradable, pero Java proporciona un mecanismo de modificación gráfica con el sistema Look and Feel, que se explicará un poco más adelante.

Los siguientes apartados realizan una pequeña visita guiada de los componentes más habituales: los que se encuentran casi en cada aplicación.

Sepa primero que todo componente gráfico presentado aquí tiene un estado activado o no (en inglés: enabled). No se puede interaccionar con un componente desactivado.

1. Botones

Los botones permiten al usuario realizar acciones o elegir entre opciones.

Cada tipo de botón dispone de un estado: seleccionado o no (en inglés: selected).

a. JButton

La clase JButton permite mostrar un botón muy sencillo con un texto y eventualmente un ícono.

Se trata de uno de los tipos más sencillos de componente gráfico de interacción con el usuario: hace clic en el botón (es decir que lo selecciona), se ejecuta una acción, el botón vuelve a su estado inicial (no seleccionado).

images/04eit01.png

b. JCheckBox

La clase JCheckBox permite al usuario realizar una elección binaria, es decir una elección entre dos opciones con exclusión mutua: sí o no, verdadero o falso, caliente o frío,...

images/04eit02.png

La casilla a marcar se queda en el estado en el que el usuario la ha dejado. Tiene un efecto de memoria, como los botones radio.

c. JRadioButton

La clase JRadioButton permite al usuario elegir entre varias opciones (por lo tanto potencialmente más de dos) con exclusión mutua: elegir un color entre rojo, verde o azul, elegir entre el lector de CD o la radio de Internet, elegir entre una tarta, un pastel o una crema catalana,...

El nombre de estos botones proviene de las antiguas radios: cuando se apretaba un botón, los demás botones saltaban. De la misma manera, en un grupo de JRadioButton, cuando se selecciona...

Threads

Se codifica un programa para que las instrucciones se ejecuten secuencialmente.

 Reproduzca el siguiente código:

public class Secuencial {  
   public static void main(String[] args) {   
         System.out.println("1era línea");  
         System.out.println("2nda línea");  
         System.out.println("3era línea");  
   }  
} 

En este código, la primera línea aparecerá siempre antes que la segunda. La tercera línea se ejecutará siempre la última.

Esto lo garantiza Java ya que ejecuta estas instrucciones en un hilo de ejecución, un Thread.

 Coloque un punto de interrupción (un breakpoint) haciendo doble clic en el margen izquierdo del editor en la segunda línea del método main(). Aparecerá un punto azul. Si no, sitúe el cursor sobre esta línea y utilice la combinación de teclas [Ctrl][Shift] B.

 Ejecute el programa en modo depurador presionando la tecla [F11]. Se cambia a la perspectiva de depurador.

images/04eit45.png

Se muestra el hilo de ejecución principal de este programa en la vista Debug. Este hilo, o thread, tiene un nombre: main.

Dentro de este hilo, las instrucciones se ejecutan secuencialmente, una después de las otras.

Sin embargo, generalmente no es lo que un usuario espera de una aplicación: muchas veces quiere poder descargar archivos al mismo tiempo que imprime un documento mientras escucha su canción favorita...

En Java, es posible crear tales aplicaciones: cada parte de la aplicación tendrá un thread dedicado, fuera del hilo de ejecución principal, que ejecutará sus propias instrucciones secuencialmente. Esto dará al usuario la posibilidad de realizar tareas en paralelo.

Existen varias maneras de ejecutar hilos paralelos. Implica en la mayor parte de los casos encapsular el código a ejecutar dentro de un objeto que implemente la interfaz Runnable.

    Runnable run = new Runnable() {   
  
         @Override   
         public void run() {  
                System.out.println("línea...

Anotaciones

Las anotaciones son metadatos que se pueden añadir directamente en el código de una aplicación Java, en casi todos los niveles: en los packages, las clases, los métodos, los atributos, los parámetros y las variables locales.

Siempre se declaran las anotaciones empezando por el carácter @.

Ya se han visto ejemplos de anotaciones, sobre todo @Override en algunos métodos.

@Override es una anotación de ayuda al compilador para indicar que el método anotado sobrecarga un método de la súper clase o de una interfaz. Si el desarrollador comete un error de tipografía en el nombre de la clase, el compilador señalará este error.

Para anotar un código, debe escribir la anotación inmediatamente antes de la sección de código afectada.

Por ejemplo, si desea anotar un método como @Deprecated (aparecerá entonces tachado en todo código que lo utilice), haga lo siguiente:

@Deprecated   
public void metodoNoMuyAcertado() {   
   ...   
} 

Las anotaciones son útiles en dos fases distintas del programa: en su compilación y en su ejecución.

En su compilación, el compilador puede emitir avisos o directamente parar la compilación. Es posible también para herramientas externas usar las anotaciones para generar automáticamente otras clases. Se trata...

Otras nociones

¡El recorrido de las clases disponibles en Java no ha acabado, lejos de eso!

Queda explorar las posibilidades y sutilidades de los packages:

  • java.net

  • java.io

  • java.nio

  • java.util.concurrent

  • java.util.logging

  • java.util.regex

  • java.util.zip

  • javax.accessibility

  • javax.print

  • javax.imageio

¡por citar unos pocos!

Java proporciona a los desarrolladores un entorno rico y muy potente que permite hacer muchas cosas. Además, existen muchas librerías (libres o de pago) disponibles para completar y enriquecer aún más las posibilidades de programación.

El proyecto que sirve de hilo conductor a lo largo de este libro no utiliza las clases de estos packages. Por lo tanto, no se profundizará en estas nociones.