Guia Java
Guia Java
PID_00273929
Ninguna parte de esta publicación, incluido el diseño general y la cubierta, puede ser copiada,
reproducida, almacenada o transmitida de ninguna forma, ni por ningún medio, sea este eléctrico,
mecánico, óptico, grabación, fotocopia, o cualquier otro, sin la previa autorización escrita
del titular de los derechos.
© FUOC • PID_00273929 Guía de Java
Índice
Introducción............................................................................................... 7
4. Avanzado.............................................................................................. 156
4.1. La clase String............................................................................ 156
4.1.1. StringBuilder frente a StringBuffer...................... 158
4.1.2. Resumen ......................................................................... 158
© FUOC • PID_00273929 Guía de Java
5. Extras..................................................................................................... 169
5.1. Javadoc ........................................................................................ 169
5.1.1. Generar javadoc por línea de comandos ..................... 170
5.1.2. Generar javadoc con Eclipse ....................................... 171
5.2. JavaFX .......................................................................................... 174
5.2.1. Introducción .................................................................. 174
5.2.2. Configuración de JavaFX en Eclipse IDE ....................... 174
5.2.3. Modelo en JavaFX ......................................................... 178
5.2.4. Vistas en JavaFX (parte visual) ...................................... 178
5.2.5. Vistas en JavaFX (parte interactiva) ............................... 180
5.3. JUnit 5 ......................................................................................... 183
5.3.1. Test unitario ................................................................... 183
5.3.2. Configuración del plugin JUnit ...................................... 184
5.3.3. Usando JUnit ................................................................. 186
Bibliografía................................................................................................. 195
© FUOC • PID_00273929 7 Guía de Java
Introducción
1)�Breve�historia
(1)
El lenguaje de programación Java apareció en 1995 de la mano de James Gos- En inglés, oak.
ling de la empresa Sun Microsystems, que fue adquirida por Oracle en 2010.
Dicen que originalmente iba a llamarse Oak, debido a un árbol de roble1 que
había en el jardín al cual daba el despacho de Gosling. Más tarde fue bautizado
como Green, para finalmente llamarse Java. Dicen que el nombre de Java fue
inspirado por el café de tipo Java que servían en la cafetería a la que solía ir
Gosling con sus compañeros. De ahí que el logo de Java sea una taza humean-
te de café.
2)�Ediciones�de�Java
(2)
En la actualidad existen cuatro plataformas o ediciones de Java que están des- Acrónimo de application pro-
gramming interface.
tinadas a construir diferentes tipos de aplicaciones. Cada plataforma Java está
compuesta por una máquina virtual, llamada Java Virtual Machine (JVM), y un
conjunto de librerías o API.2
(3)
d)�Java�Card. Esta edición es un subconjunto de la Standard Edition y se usa En inglés, smart cards.
3
para ejecutar aplicaciones de manera segura en tarjetas inteligentes o dispo-
sitivos con pequeña capacidad de almacenamiento. Esta tecnología se utiliza
por ejemplo en tarjetas monedero.
3)�Ciclo�de�vida�de�un�programa�Java
Con este paso se consigue convertir el código escrito en Java a un código escrito
en un lenguaje intermedio llamado bytecode. De hecho, Java Compiler crea un
fichero nuevo llamado igual que el fichero .java original, pero con extensión
.class. Gráficamente sería:
Figura 1
© FUOC • PID_00273929 9 Guía de Java
Se podría decir que el código bytecode es similar al código�máquina que los Bytecode
compiladores de otros lenguajes de programación, como C/C++, generan. La
El bytecode no es entendido
diferencia radica en que el código bytecode no está destinado para ejecutarse por ninguna arquitectura hard-
en una arquitectura de hardware concreta (por ejemplo, Intel es diferente a ware, sino por la JVM. Es me-
nos complejo (y más rápido)
Macintosh), sino que está pensado para ejecutarse en una máquina virtual Ja- para la JVM interpretar byteco-
de que directamente código
va concreta (Java Virtual Machine, JVM). Java�Virtual�Machine (JVM) no es Java.
más que un intérprete de bytecode. Esto conlleva a que durante la fase de inter-
pretación exista una ralentización en el rendimiento del programa respecto al
mismo programa escrito en lenguaje máquina que generan los compiladores
de lenguajes como C/C++.
(4)
La JVM solo es dependiente del sistema operativo, pero no de la máquina/or- En español, «Escribe una vez,
ejecuta en cualquier parte».
denador en el que se esté ejecutando. Así pues, lo único que se debe hacer es
tener instalada la JVM del sistema operativo correspondiente. Gracias a esto,
se desarrolla una vez en cualquier dispositivo (ficheros .java), en dicho dis-
positivo se compila/transforma el código Java en un código estándar e inter-
medio llamado bytecode (ficheros .class) y, finalmente, este código se ejecuta
en cualquier dispositivo equipado con la máquina virtual (JVM). De ahí que
el eslogan de Java sea: «Write once, run anywhere».4 Gráficamente este proceso
sería:
Figura 2
¿Java es compilado o
interpretado?
Para que la JVM ejecute el bytecode de un programa, hay que llamar al fichero Método main
.class cuyo homónimo .java tuviera el método main. Esta llamada se hace
Más adelante veremos qué es
de la manera siguiente: el método especial main de un
programa.
4)�JRE�frente�a�JDK
El JDK debe ser instalado por todo aquel que quiera desarrollar programas en
Java.
© FUOC • PID_00273929 11 Guía de Java
Antes de ver los pasos generales necesarios para instalar el JDK para Windows,
Linux y macOS, cabe enfatizar que desde la versión 11 del JDK, la licencia de
uso cambió. Si antes era completamente gratuito, desde JDK 11 su utilización
solo es gratuita para uso personal, pero no para desarrollar aplicaciones para
terceros. Asimismo, el soporte por parte de Oracle –como pueden ser actua-
lizaciones–, tampoco es gratuito. Es por esto por lo que desde la versión 11,
muchas personas prefieran instalarse la versión libre llamada OpenJDK.
Lanzamiento�de�nueva�release�(ver- Cada seis meses. Cada tres años se lanza una versión LTS (long
sión) term support), por ejemplo, JDK 8 y JDK 11,
que añade grandes cambios y recibe soporte
(por ejemplo, actualizaciones, corrección de
bugs, etc.) durante ocho años.
Cada seis meses aparece una versión no-LTS
que añade mejoras sobre la última versión
LTS, pero estas mejoras no impactan en el co-
re. Cada versión no-LTS deja de tener soporte
a los seis meses, es decir, cuando aparece una
nueva versión.
Actualizaciones (por ejemplo, correc- Cada seis meses. Inmediato si se paga una licencia. En caso
ción de bugs) contrario, cada seis meses.
Encargado�del�mantenimiento Comunidad Java, Oracle, Red Hat, IBM, etc. Oracle Corporation.
© FUOC • PID_00273929 12 Guía de Java
1) Descargad la versión de JDK 13.0.2 o superior. Sobre todo, hay que fijarse en
que se trata del JDK y no del JRE. Acceded a través del navegador a la siguiente
dirección: http://java.sun.com/javase/downloads/index.jsp.
Figura 3
Haced clic en uno de los dos botones de «Download» (resaltados con un re-
cuadro en la figura 3). A continuación, aceptad la licencia y escoged la opción
de Windows que deseéis. Os recomendamos que elijáis la opción ejecutable
(*.exe).
© FUOC • PID_00273929 13 Guía de Java
Figura 4
Nota
Si tenéis Windows XP o un
ordenador con CPU de 32
bits, debeis descargar la ver-
sión 8 (8u221), que tiene
opción de 32 bits (x86):
https://www.oracle.com/
technetwork/java/java-
se/downloads/jdk8-down-
loads-2133151.html. En prin-
cipio podréis realizar todas las
actividades iniciales, pero es
importante que consigáis un
ordenador con CPU de 64 bits
y con un sistema operativo
Windows 7 o superior.
Figura 5
Figura 6
Figura 7
Figura 8
Figura 9
Figura 10
Figura 11
Nota
10) Finalmente, para comprobar que JDK está correctamente instalado, podéis
consultar (por línea de comandos, por ejemplo, cmd) las versiones de java y
javac mediante las instrucciones:
Figura 12
El sistema operativo macOS ya trae por defecto la versión completa del JDK
instalada y preparada para usarla. En el caso de que se tenga una versión vieja
del JDK y se quiera instalar una más nueva, se puede conseguir a través de las
actualizaciones de software y el website de soporte de Apple.
© FUOC • PID_00273929 18 Guía de Java
En primer lugar vamos a ver cómo se compila un programa Java por línea de
comandos. Es decir, vamos a ejecutar el compilador del JDK para que traduz-
ca de lenguaje Java a bytecode. O, lo que es lo mismo, crearemos un fichero
.class a partir de un fichero .java.
Actividad
(5)
Si habéis hecho la actividad propuesta como Tarea 1 de la PEC 1, os habréis Acrónimo de integrated develop-
ment environment, entorno integra-
dado cuenta de que podemos escribir código con un editor de texto sencillo –
do de desarrollo.
por ejemplo, Notepad– así como compilar y ejecutar programas Java por línea
de comandos usando javac y java, respectivamente. Sin embargo, cuando
los programas se hacen más complejos, este entorno de desarrollo no es prác-
tico. Es por eso por lo que se crearon los IDE,5 que facilitan el desarrollo de
software. IDE hay muchos. Los más utilizados en el ámbito del lenguaje Java
son Eclipse, NetBeans e IntelliJ. Son todos muy similares y usar uno u otro
dependerá de las preferencias de cada desarrollador. En esta guía vamos a op-
tar por Eclipse.
En primer lugar debemos instalar Eclipse IDE. Para ello, debemos seguir los
pasos detallados en el apartado siguiente.
La instalación de Eclipse es bastante sencilla, solo debéis seguir los pasos que
se detallan a continuación (para sistemas operativos diferentes a Windows,
quizás es más sencillo bajarse directamente el fichero comprimido de la mo-
dalidad de Eclipse IDE for Java Developers y descomprimirlo en el ordenador).
Algunos aspectos o nombres pueden cambiar entre lo que se explica en este
apartado y lo que podéis encontrar en el entorno de trabajo. En tal caso, debéis
ser capaces de hacer las analogías correspondientes.
1) En primer lugar, debéis tener instalada la versión JDK más actual posible.
(6)
3) Una vez en ella, usaremos la manera más sencilla de instalar Eclipse. Es En inglés, Eclipse Installer.
6
decir, mediante el instalador Eclipse (véase recuadro de la figura 13). Hacemos
clic en el enlace de nuestro sistema operativo.
Figura 13
4) La web nos llevará a otra página con un botón en grande que dice «Down-
load». Hacemos clic en él.
Figura 14
Figura 15
9) Una vez escogida la modalidad, nos aparecerá una ventana que nos pedi-
rá dónde queremos guardar Eclipse, si queremos una entrada en el menú de
inicio y un acceso directo en el escritorio. Además, no detecta la versión de
Java que tenemos por defecto en el sistema operativo. En el campo «Product
Version» escoged la versión «Lastest». Cuando tengamos nuestras preferencias
indicadas, decimos «Install».
10) Durante la instalación nos aparecerá una ventana para aceptar la licencia
de uso. Hacemos clic en «Accept». La instalación continuará. También es po-
sible que nos pida aceptar unos certificados. Los aceptamos y la instalación
seguirá su curso.
12) Tras cargar librerías, nos aparecerá una ventana en la que nos pide la ubi-
cación (es decir, carpeta) que Eclipse debe usar como directorio de trabajo o
workspace. Es decir, allí donde guardará y buscará los proyectos (es decir, los
programas que creemos), además de guardar nuestras preferencias. Podemos
dejar la ubicación que nos sugiere o escoger otra. También podemos decirle
que la recuerde y no nos pregunte más (recomendamos no marcar esta op-
ción). Una vez tengamos claro dónde ubicar nuestro directorio de trabajo, ha-
cemos clic en el botón «OK».
© FUOC • PID_00273929 21 Guía de Java
Figura 16
Figura 17
1.3.2. Vistas
maximizar. Cuando maximizamos una vista, todas las otras vistas se minimi-
zan. Para volver a la configuración normal podemos volver a hacer doble clic
en la barra superior de la vista o bien clicar el botón situado en la parte más
derecha de la barra superior de la vista, que tiene la forma de dos ventanas
sobrepuestas. En la perspectiva de Java, cuando abrimos por primera vez un
nuevo proyecto, nos encontramos con las vistas siguientes:
Figura 18
Editor
Outline
La vista de outline (recuadro azul de la figura 18) nos permite visualizar los
componentes del fichero Java que tengamos abierto en el editor, de la misma
forma en que se visualizan en el explorador de paquetes. Es decir, se nos mues-
tran las clases, atributos y métodos que haya en el fichero .java actual. Si se
clica en cualquiera de estos elementos en la vista de outline, inmediatamente
se resalta en la ventana del editor. Los botones que hay encima de la vista nos
permiten ocultar o mostrar diferentes elementos, como métodos no públicos,
métodos estáticos, atributos, etc. También nos permite ordenar los elementos
en la vista por orden alfabético.
© FUOC • PID_00273929 24 Guía de Java
Problems
Esta vista (recuadro naranja de la figura 18) se suele encontrar en la parte in-
ferior del editor y nos muestra los errores y avisos que tengamos en cualquier
fichero de los proyectos que tengamos abiertos. Para cada problema nos mues-
tra una descripción y el lugar donde se encuentra (fichero y línea).
Console
Esta vista se suele encontrar también en la parte inferior del editor, en otra
pestaña junto a la de problemas (recuadro naranja de la figura 18). Esta vista
se activa automáticamente cuando se ejecuta una aplicación Java y sirve para
mostrar la salida del programa, así como para pedir la entrada de datos a la
aplicación si hace falta.
1.3.3. Menús
Menú «File»
Desde este menú podemos acceder a los asistentes de creación de nuevas cla-
ses, proyectos, paquetes, interfaces y cualquier otro elemento relacionado con
Java. Para ello solo hay que abrir el submenú «New» y desde allí seleccionar
el asistente que se desee.
Desde el menú «File» tenemos acceso a todas las operaciones sobre los fiche-
ros de nuestro proyecto. Si estamos situados sobre algún fichero en la vista de
explorador de paquetes, podremos mover el fichero o renombrarlo. También
existe la opción de refrescar («Refresh») las carpetas del explorador. Esta fun-
cionalidad se puede realizar también usando el atajo de teclado F5. Tanto si
estamos en esta vista o sobre el editor, podremos cerrar el fichero seleccionado
o guardarlo con otro nombre.
© FUOC • PID_00273929 25 Guía de Java
Figura 19
Dentro del asistente de exportación hay que destacar también el apartado «Ja-
va». Tenemos tres opciones:
Menú «Project»
El menú «Project» nos muestra opciones que nos permiten especificar cómo
queremos que se lleve a cabo la compilación del proyecto. La última opción de
todas es la de propiedades del proyecto («Properties»). Desde «Properties» se
puede configurar cualquier aspecto del proyecto. Las opciones más destacadas
son:
En el apartado de librerías siempre sale por defecto como mínimo las librerías
de sistema de la máquina virtual que estemos usando. Si se edita esta librería
tendremos la opción de cambiarla por cualquier otra de las máquinas virtuales
de Java que tengamos en nuestro ordenador. Se pueden añadir otras librerías
que hagan falta en nuestro proyecto desde ficheros JAR o desde directorios.
© FUOC • PID_00273929 27 Guía de Java
Menú «Run»
Desde este menú podemos ejecutar nuestro código, gestionar diferentes con-
figuraciones de ejecución del proyecto y llamar al debugger.
Otros menús
En los menús «Source» y «Refactor» hay toda una serie de opciones con tal de
automatizar ciertas tareas repetitivas a la hora de desarrollar una aplicación.
En el menú «Source» podemos encontrar utilidades como añadir bloques de
comentarios, generar setters y getters automáticamente, añadir de forma auto-
mática los imports que hagan falta en el fichero, crear la indentación del códi-
go de forma automática, etc. En el menú «Refactor» tenemos utilidades para
propagar cambios en el código sobre todos los ficheros .java implicados, co-
mo por ejemplo, cambiar el nombre de una clase o método y cambiar todas
© FUOC • PID_00273929 28 Guía de Java
las referencias y declaraciones que haya en el código. Desde aquí también po-
demos mover una clase de un paquete a otro y propagar los cambios que esto
produzca en las demás clases del proyecto.
© FUOC • PID_00273929 29 Guía de Java
Además de servir para documentar, los comentarios también sirven para «ca-
par» o «inhabilitar» parte de nuestro código. Así pues, en vez de borrar un
trozo de código para siempre, podemos comentarlo y, si fuera necesario, recu-
perarlo más tarde. Una vez sepamos que ese código es descartable, podemos
borrarlo para siempre.
Esto es útil, por ejemplo, cuando estamos probando cosas. También es útil
si estamos encallados escribiendo un algoritmo complejo, pero tenemos un
código base creado que sabemos que funciona. Ese código base podría ser co-
mentado para recuperarlo más tarde si al escribir el algoritmo llegamos a un
callejón sin salida y debemos empezar de nuevo. De esta manera, el código
base sirve como punto de partida desde donde iniciar de nuevo el algoritmo.
ClassName.java
1) Debido a que Java está orientado a objetos, los programas en Java están
organizados por clases. Cada�clase�tiene�que�ir�en�un�fichero�cuyo�nombre
debe� ser� el� mismo� que� el� de� la� clase. Por una cuestión de convención de
nombres, lo más habitual es que la primera letra de cada palabra que compone
el nombre de la clase empiece por mayúscula. En el caso del ejemplo, la clase
se llama ClassName y el fichero ClassName.java.
2) Un programa de una cierta envergadura estará formado por más de una clase Uso de varargs en el main
(por ejemplo, más de un fichero .java). Esto obliga a que, en como mínimo
Como veremos más adelan-
una de esas clases, haya un método especial llamado main. Dicho método sirve te, String[] args se puede
de punto de entrada al programa. Es decir, el programa se iniciará llamando a cambiar por String...args,
gracias a la nueva sintaxis de
este método. El método main tiene una firma concreta: las varargs.
4) En este caso, args tendría dos casillas con los valores "David" y "36", los
dos serían texto (es decir, String). El primer valor estaría en args[0] y el
segundo en args[1].
do Java), las�firmas�de�todas�las�funciones�deberán�tener�la�palabra�static.
Cuando pasemos a programar orientado a objetos, veremos que este requisito
ya no será necesario.
6) A continuación de los dos comentarios en los que pone TODO (del inglés,
to do, es decir, «para hacer», «pendiente», etc.) podríamos escribir el código de
nuestro programa. El código del primer TODO estará formado por instruccio-
nes/sentencias (statements) y bloques (blocks).
La clase System de la API Java nos proporciona acceso a tres atributos públicos Nota
y estáticos llamados in, out y err. Tanto System.in, como System.out
En este apartado solo veremos
y System.err son instanciados e inicializados automáticamente por la JVM los métodos básicos de la en-
cuando esta arranca, así que no hay que hacer nada para tenerlos disponibles. trada y la salida estándares,
obviando la salida de error.
Estos dos métodos son los más frecuentes. La diferencia entre ellos es que
println imprime el contenido que se le pase y añade un salto de línea (\n).
Es decir, avanza el cursor para escribir en la siguiente línea de la pantalla. Sa-
biendo esto, el siguiente código:
© FUOC • PID_00273929 33 Guía de Java
Imprimiría:
Método printf
• d para entero,
• f para float o double,
• c para char y
• s para String (cadena de texto).
Respecto al salto de línea, este método funciona exactamente igual que print,
es decir, no avanza el cursor a la siguiente línea. Si queremos hacer un salto,
debemos añadir \n al final del texto.
© FUOC • PID_00273929 34 Guía de Java
Como ya hemos dicho, por defecto, el flujo de entrada viene del teclado. En
JDK 5 se introdujo una clase llamada Scanner que facilita la lectura con for-
mato del teclado. De este modo, con Scanner podremos controlar, de manera
sencilla, si queremos leer un int, un double, etc.
2.4. Variables
Se llama variable porque el valor que almacena puede variar (es decir,
cambiar) durante la ejecución del programa.
2.4.1. Nombre
Los nombres de las variables deben ser representativos de aquello que alma-
cenan.
Imaginad que tenemos tres variables con los siguientes tres nombres: n, p1 y vC. ¿Sabéis
qué almacenan? Ni idea, ¿verdad? Así pues, estos nombres tan crípticos no son adecua-
dos, aunque sí correctos sintácticamente (es decir, Java no dará error). Sin embargo, si
cambiamos los nombres de las variables anteriores por estos otros: numberPeople, per-
son1 y viewsCount ahora sí que podríais saber qué guardan. Como podéis ver, escoger
un nombre que sea descriptivo es una buena práctica.
• Los nombres de las variables son case-sensitive. Es decir, Java distingue entre
minúsculas y mayúsculas, por lo que numPeople y numpeople no son la
misma variable.
• El nombre de una variable debe empezar o con una letra, o con el símbolo
$, o con el símbolo underscore _. No obstante, se ha generalizado la buena
práctica de que el nombre de las variables empiecen por una letra, no por
$ ni _.
© FUOC • PID_00273929 35 Guía de Java
2.4.2. Tipos
Como hemos comentado, las variables serán de un tipo. En Java existen dos
tipos: primitivos y referencias. Los primitivos (o básicos) sirven para almace-
nar valores sencillos, mientras que las referencias se utilizan para guardar ele-
mentos complejos como son los objetos de una clase.
© FUOC • PID_00273929 36 Guía de Java
Primitivos
Tipo Bytes necesarios pa- Intervalo/rango Valor por defecto Utilidad Ejemplos
ra guardar el valor (valores posibles)
short 2 (16 bits) [-32768, 32767] 0 Mismo uso que byte, 32769, 0xffea
pero su tamaño es el
doble.
char 2 (16 bits) [’\u0000’, ’\u0000’ Guardar un símbolo de ‘a’, ‘?’, ‘\101’, ‘\n’
’\uffff’] la codificación Unico-
(internamente se de.
guarda como un
entero [0, 65535])
int 4 (32 bits) [-2147483648, 0 Guardar valores ente- -2, -1, 0, 1, 234,
2147483647] ros. 500
float 4 (32 bits) Estándar para arit- 0.0 Guardar valores deci- 1.23e100f,
mética en coma males con 6-7 decima- -0.23e-100f, .5f
flotante de 32 bits les significativos.
(IEEE 754).
double 8 (64 bits) Estándar para arit- 0.0 Guardar valores deci- 1.23456e300d,
mética en coma males con 15 decima- 1e1d
flotante de 64 bits les significativos.
(IEEE 754).
Unicode
Para declarar una variable seguiremos uno de los siguientes patrones (y com-
binaciones de ellos):
Por ejemplo:
© FUOC • PID_00273929 37 Guía de Java
Por otro lado, en el caso de datos numéricos, Java permite, para facilitar su
lectura, separarlos mediante el símbolo _ (underscore), por ejemplo, 123_456,
12_345, 1_3 (sería el número 13), etc. Es justo lo que se ha hecho en la
variable viewsCount.
En este punto hay que tener presente una apreciación con los tipos long y
float. Veamos el ejemplo siguiente:
Las dos declaraciones anteriores lanzarán un error indicando que el valor que
se desea asignar a year y a price es incompatible. ¿Cómo es esto posible
si están dentro del rango/intervalo de valores? Sencillamente porque el com-
© FUOC • PID_00273929 38 Guía de Java
pilador de Java considera los valores 1983 y 9.99 como valores de tipo int
y double, respectivamente. Así pues, para forzar que los considere long y
float, respectivamente, debemos añadir la letra L en el primer caso, y la letra
F en el segundo. En ambos casos la letra puede ser mayúscula o minúscula:
Las variables de tipo char guardan un único carácter. Estos valores se delimi- JDK 10 y posterior
tan con una comilla simple. En el caso de las variables de tipo boolean, los
Desde JDK 10 es posible no in-
valores que pueden aceptar son true o false. dicar el tipo (ya sea primitivo o
referencia) de una variable lo-
cal utilizando la palabra reser-
Cuando declaramos una variable de tipo primitivo, lo que hace el compilador vada var. Una restricción de
usar var es que la variable de-
es asignarle una dirección de memoria del stack a dicha variable. Nosotros be ser inicializada para que el
compilador pueda inferir el ti-
como programadores no tenemos control de en qué dirección de memoria se po de la variable, por ejemplo:
crea esta variable. var v1 = 5;.
Referencias
Todas las variables que no son declaradas como uno de los ocho tipos primi-
tivos anteriores, son referencias. En el siguiente ejemplo vamos a declarar un
array de int.
El operador new crea un objeto en una dirección de memoria concreta del heap Stack y heap
y esa dirección de memoria se la asigna a la referencia, la cual se guarda en
Los conceptos de stack y heap
una zona de memoria del stack. Así pues, el valor de una referencia será una se estudian en profundidad en
dirección de memoria. asignaturas relacionadas con
los sistemas operativos.
Así pues, grades es una variable de tipo referencia ubicada en la posición 854
del stack y cuyo valor es la dirección de memoria (valor 1004) de un objeto
array de 5 casillas de tipo int que está en el heap.
Cuando un programa se ejecuta, la JVM crea un hilo de ejecución (en inglés, thread) con
un stack privado. En el stack se guardan variables locales, variables de referencia, el control
de las invocaciones a métodos, los valores de los parámetros y de retorno de un método,
resultados parciales, etc. Este espacio de memoria se rige por un comportamiento de pila
(stack), es decir, LIFO (acrónimo de Last-In, First-Out).
© FUOC • PID_00273929 40 Guía de Java
En cambio, el heap es un espacio de memoria dinámica que la JVM adquiere solo arran-
carse la JVM y es único. Por lo tanto, el heap es común a todos los threads de ejecución.
En el heap se guardan los objetos.
Por todo lo anterior, el tamaño del stack es mucho menor que el del heap.
Así pues, una vez creado el objeto de tipo array de int, podríamos asignar un
valor a una de sus casillas:
Hemos asignado el valor 10 a la cuarta casilla del array grades. De igual ma-
nera que ocurre en otros lenguajes (por ejemplo, C/C++), la primera casilla de
un array en Java se corresponde con el índice 0.
Si miramos la última línea del código anterior, vemos que lo que se imprime
por pantalla no es ningún valor del array, sino un «texto extraño». Ese «texto
extraño» no es más que la dirección de memoria del heap donde está alojado
el objeto, que es un array de int.
El símbolo * nos está diciendo que agePointer es una variable que almace-
na la dirección de memoria –es decir, un puntero– de otra variable que es de
tipo int. Como agePointer es una variable, el compilador le asignará una
dirección de memoria del stack:
© FUOC • PID_00273929 41 Guía de Java
A partir de aquí, puedo usar tanto age como agePointer para modificar el
valor de age, ya que agePointer sabe en qué dirección de memoria está guar-
dado el valor de la variable age. Así pues:
Sin embargo, el uso de punteros también trae consigo problemas, tales como:
Punteros en Java
Una referencia�en�Java es como un puntero de C/C++ con la sintaxis
de un tipo primitivo y con unas restricciones más fuertes. Java no tiene punteros explíci-
tamente, pero sí implícitamen-
te. Es decir, las referencias es-
tán programadas internamen-
te como punteros, pero estos
A partir de la definición anterior, para declarar una referencia declararemos punteros no son accesibles pa-
ra los programadores.
una variable de la misma manera que hacíamos con los tipos primitivos, pero
ahora el tipo será un array o una clase (ya sea propia de la API de Java o creada
por nosotros).
Cuando se declara una variable de tipo referencia sin usar el operador new,
el valor por defecto es null. Así pues, en el ejemplo anterior, tanto grades
como birthdate y david son tres referencias que no apuntan a ninguna
zona de memoria del heap, es decir, son null. En cambio, la JVM sí ha creado
un objeto de tipo array con 3 elementos Person. La dirección de memoria del
heap de ese objeto se lo ha asignado como valor a la referencia people. Por lo
tanto, people es una referencia a una zona de memoria del heap, por ejemplo,
I@78ab8876. Sin embargo, cada casilla del array people es una referencia
que no apunta a ninguna zona del heap, es decir, son null. En el caso de las
© FUOC • PID_00273929 43 Guía de Java
variables grades, birthdate y david, así como para cada casilla de people,
aún no se les ha asignado un objeto con el operador new (o asignándole otra
referencia) y apuntan a null. Sin embargo, si hacemos:
2.5. Operadores
Igual que en las matemáticas, los operadores son símbolos del lenguaje
que indican que debe realizarse una operación específica sobre un cierto
número de operandos y devolver un resultado fruto de aplicar dicha
operación a los operandos. Por su parte, un operando es una entrada
de un operador.
2 + 7 − add(8,2)
−1
(3 * 2) + 8
Operador Precedencia
multiplicativo * / %
adición + -
igualdad == !=
bitwise�AND &
bitwise�OR�exclusiva ^
bitwise�OR�inclusiva |
AND�condicional &&
OR�condiconal ||
ternaria ? :
Los operadores que están en la misma fila en la tabla 3 tienen la misma prece-
dencia. En este caso, la regla que se sigue es que todos los operadores binarios
(es decir, que tienen dos operandos), excepto los de asignación, son evaluados
de izquierda a derecha, mientras que los operadores de asignación (última fila
de la tabla), son evaluados de derecha a izquierda. Asimismo, los paréntesis,
igual que en las matemáticas, tienen la máxima precedencia y se suelen usar
para cambiar el orden de evaluación. Veamos algunos ejemplos de expresiones
para entender las reglas de precedencia:
1+2-3+4 ((1+2)-3)+4 4
• La asignación simple.
• Los operadores aritméticos.
• Asignaciones aumentadas o compuestas.
• Los operadores unarios.
Asignación simple
Operadores aritméticos
Llegados a este punto hay que tener presente el valor resultante de aplicar un
operador. En este caso debemos distinguir varias casuísticas:
La regla anterior afecta a la división entre enteros (int), ya que el resultado Cast explícito
real podría ser un número decimal (es decir, float o double), pero el resul-
Normalmente el cast explíci-
tado obtenido aplicando la regla anterior es que será un entero (es decir, sin to consiste en indicar al com-
decimales). Así pues, 1/2 = 0. Sin embargo, 1.0/2.0 = 0.5. En este caso, pilador que queremos que se
realice una pérdida de pre-
si queremos forzar que el resultado sea un float o un double, debemos ha- cisión (truncado) al pasar de
un tipo a otro más pequeño
cer un cast explícito, es decir, una conversión explícita del tipo, por ejemplo, y que somos completamen-
te conscientes de esta posi-
(float) (1/2) = 0.5. ble pérdida durante la conver-
sión, por ejemplo, int a =
(int)3.5; //a valdrá 3.
2)�Operandos�del�mismo�tipo�(casos�byte,�short�y�char): si los dos ope- Si no hacemos el cast explíci-
to cuando hay posible pérdida
randos son byte o short o char, entonces las operaciones aritméticas son de precisión (por ejemplo, de
realizadas como si los operandos fuesen int (cast implícito porque no hay double, float y long hacia
int), entonces el compilador
pérdida de precisión) siendo el resultado un int. Por ejemplo, byte + byte nos dará un error.
= int + int = int. Si se quiere recuperar el tipo original, hay que hacer
una conversión explícita (es decir, un cast explícito) del resultado:
En el caso del tipo char, el valor int asignado a un carácter será su correspon-
diente número Unicode dentro del rango [0, 65535].
Para estos tres tipos –byte, short y char–, Java siempre hace las ope-
raciones aritméticas como si fuesen int. Es decir, los promociona (les
aplica un casting implícito) a int.
© FUOC • PID_00273929 48 Guía de Java
Finalmente, cabe indicar que el operador + puede usarse para concatenar (es
decir, unir) cadenas de caracteres, que en Java son objetos de la clase String
(lo veremos más adelante). Por ejemplo:
Tabla 5
Azúcar sintáctico
Asignación Ejemplo Equivalente Nuevo valor
aumentada de number El azúcar sintáctico (en inglés,
syntactic sugar) es una nue-
va sintaxis que se añade a un
+= number+=3 number = number + 3 13
lenguaje de programación pa-
ra hacer algunas operaciones
-= number-=6 number = number - 6 4 ya existentes de manera más
sencilla y abreviada. Esta nue-
*= number*=3 number = number * 3 30 va sintaxis hace «más dulce»
el lenguaje. Un ejemplo es el
operador +=.
/= number/=2 number = number / 2 5
Existe una pequeña diferencia entre los operadores simples (+, -, *, /, %) y los
compuestos que acabamos de ver:
Operadores unarios
Como podemos ver en la tabla 6, una de las particularidades que tienen los
operadores ++ y -- es que pueden aplicarse como prefijo o sufijo. Veamos un
ejemplo más extenso de las diferencias:
© FUOC • PID_00273929 50 Guía de Java
Operadores condicionales
En el caso del AND condicional, este devuelve true si los dos operandos son
true. Mientras que el operador OR condicional devuelve true cuando al me-
nos uno de los dos operandos es true. Estos dos operadores se relacionan con
los operadores lógicos · y + del álgebra de Boole, respectivamente. También se
puede hacer su equivalencia con las puertas lógicas AND y OR, respectivamente.
Por todo lo anterior, una manera sencilla de obtener el resultado de un AND y
un OR es convertir mentalmente los operandos false en 0, los true en 1, el
© FUOC • PID_00273929 51 Guía de Java
Operador instanceof
Este operador compara un objeto con un tipo de clase concreta. Es decir, se Clase e interfaz
usa para saber si una instancia/objeto pertenece a una clase concreta (ya sea
Estos dos conceptos se verán
padre directo o no) o implementa una determinada interfaz. más adelante tanto en esta
guía como en los módulos teó-
ricos de la asignatura.
Un ejemplo de uso de este operador podría ser:
© FUOC • PID_00273929 52 Guía de Java
En este apartado veremos operadores que trabajan directamente con los bits.
Estos operadores son mucho menos utilizados que los vistos hasta ahora y se
clasifican en:
Operadores bitwise
& AND de bits byte, short, int, (3 == 3.0) & (50<100) true
long, char (tratado co- false & true false
mo int) y boolean 60 & 13 12
~ Complemento a 1 byte, short, int, long ~00000000 (es un byte) 11111111 (byte)
y char ~5 (siendo 5 un int: 0101) -6 (int)
Los operadores AND (&) y OR (|) de bits cuando los operandos son boolean
actúan como hemos comentado con sus homónimos condicionales, pero sin
el comportamiento de «cortocircuito». Es decir, independientemente del valor
de la expresión del primer operando, el segundo operando siempre se evaluará.
Así pues:
Este operador también se puede aplicar sobre operandos cuyo resultado sea un
boolean. En este caso, cuando los dos operandos coinciden en valor el resulta-
do es false y, en caso contrario, el resultado es true. Así pues: true^true es
false, false^false es false, true^false es true y false^true es true.
El operador complemento a 1 (~) lo que hace es convertir la representación int, short, long y byte
binaria del operando y cambiar los ceros por unos y los unos por cero. En el
Los tipos int, short, long y
ejemplo de la tabla 9 los pasos internos son: byte en Java son números en
complemento a 2.
Operadores de shiftado
Un bloque condicional es aquel que sirve para ejecutar o no una serie de ins-
trucción según una condición. Dentro de este tipo de bloque, también llama-
do de decisión o selección, existen varias alternativas.
1)�Bloque�if
Este bloque sirve para indicar un conjunto de instrucciones que se deben eje-
cutar si se cumple una expresión lógica, es decir, si dicha expresión es true.
A esta expresión lógica normalmente se le suele llamar condición.
2)�Bloque�if-else
Este bloque sirve para indicar dos bloques de instrucciones que son excluyen-
tes entre sí. El primer bloque (bloque dentro del if) se ejecutará si se cumple
una expresión lógica, es decir, si dicha expresión es true. En caso contrario,
se ejecutará el segundo bloque (bloque dentro del else).
© FUOC • PID_00273929 55 Guía de Java
3)�Bloque�if�anidado�o�else-if
4)�Operador�ternario
Este operador es muy utilizado para asignar valores a una variable en función
de una condición.
5)�Bloque�switch
Los casos se evalúan de manera secuencial hasta que su valor coincide con el
del selector y entonces entra en su bloque. Así pues, según nuestro programa,
no será lo mismo poner el case 0 primero que ponerlo después del case 2,
por ejemplo. Una vez se entra en un caso, se irán ejecutando todos los bloques
hasta que uno de ellos contenga un break. Cuando se encuentra el break, el
programa sale del switch. Es decir, en el ejemplo anterior, si number hubiera
sido 1, entonces se ejecutaría el bloque del case 1, pero al no haber break, se
ejecutaría también el bloque del case 2, asignándole el valor 25 a la variable
age. Como el case 2 tiene un break, la ejecución sale del switch y pasa a la
instrucción age -= 5;. Así pues, el programa se comporta de manera idéntica
tanto si el valor de number es 1 como si es 2.
© FUOC • PID_00273929 57 Guía de Java
Desde JDK 13 en adelante se puede usar la palabra reservada yield para de-
volver un valor al terminar un caso y, por consiguiente, terminar el bloque
switch.
Este tipo de bloque, también llamado bloque de bucle, sirve para ejecutar un
conjunto de instrucciones repetidamente hasta que deja de cumplirse una con-
dición. Existen varias alternativas.
1)�Bloque�while
Ejecuta el código que hay dentro de sus llaves mientras su condición sea true.
Cuando la condición sea false, entonces finaliza el bucle y se pasan a ejecu-
tar las instrucciones que hay después del bloque while. Cabe resaltar que la
condición se evalúa en cada iteración.
2)�Bloque�do-while
Este bloque es similar al anterior pero la condición se evalúa por primera vez
cuando se han ejecutado las instrucciones que hay dentro del bloque. No obs-
tante, a cada iteración se evalúa la condición.
En el ejemplo anterior, se ejecuta una vez el código que hay dentro del bloque
do-while, puesto que la condición no se ha evaluado hasta que se pasa por
la asignación number = 3;. Una vez hecha la asignación se comprueba si la
condición es true. En este caso se ve que es false y se termina el bloque
do-while.
A diferencia del bloque while, el código que se encuentra dentro de las llaves
del do-while siempre se ejecuta como mínimo una vez, mientras que en el
caso del while puede llegar a no ejecutarse nunca.
3)�Bloque�for
4)�Bloque�enhanced for
El bloque for anterior tiene otra sintaxis diseñada para iterar sobre colecciones
llamada enhanced for. Por colección nos estamos refiriendo tanto a un array
como a una estructura de datos como puede ser una pila, una cola, etc. que
la propia API de Java proporciona.
1)�Instrucción�break
Por su parte, un break etiquetado permite finalizar aquel bloque iterativo que
tenga la etiqueta que se indica en el break.
© FUOC • PID_00273929 61 Guía de Java
2)�Instrucción�continue
3)�Instrucción�return
2.7. String
(7)
Existen diferentes secuencias de escape7 que sirven para indicar alguna ins- En inglés, escape sequences.
¿Qué ocurre si el texto tiene dentro unas comillas dobles o una contrabarra
(o barra invertida)? Pues que el compilador se confundirá porque no sabrá
qué comillas dobles son las del texto y cuáles las que limitan la cadena de
caracteres. Lo mismo con la barra invertida, no sabrá si se trata de un comando
o no. ¿Cómo solucionar este conflicto? Añadiendo una doble invertida delante
de las comillas o de la barra invertida que queremos que sea parte del texto.
© FUOC • PID_00273929 63 Guía de Java
La clase String proporciona muchos métodos, los cuales nos facilitan muchas
tareas.
Enlace recomendado
2.7.4. Conversiones
1)�De�tipo�primitivo�a�String
(8)
Otra manera de hacerlo, quizás más explícita y clara a simple vista, es usando Más adelante veremos qué es un
8 método estático.
un método estático llamado valueOf que sirve para todos los primitivos.
2)�De�tipo�String�a�tipo�primitivo
Para obtener el valor de tipo primitivo de un texto podemos usar los métodos
estáticos parseXXX que incluyen las clases wrappers de los tipos primitivos
(excepto para char).
En el caso del tipo char, debemos usar el método charAt de la clase String
que hemos visto anteriormente.
2.8. Arrays
Cuando el array tiene una única dimensión se le suele llamar array, vector o
arreglo. Cuando el array tiene dos dimensiones se le llama array de dos dimen-
siones, matriz o tabla. Cuando el número de dimensiones es mayor se le llama
array de N dimensiones (siendo N el número de dimensiones), array de arrays
o, simplemente, array multidimensional. También puede darse el caso de que
el número de columnas en un array multidimensional sea desigual, dándose
lo que en inglés se conoce como jagged array.
© FUOC • PID_00273929 67 Guía de Java
Un array será un objeto que estará alojado en el heap. Para poder acceder a
dicho objeto tendremos una variable de tipo referencia. El nombre de un array
suele ser un sustantivo en plural.
Por lo que respecta a la referencia people aún faltan por inicializar las dos filas:
Figura 20
En Java, los arrays permiten ser inicializados de una manera especial mediante
el uso de llaves ({}). Por ejemplo:
© FUOC • PID_00273929 68 Guía de Java
Con la línea anterior hemos creado un array de cinco casillas de tipo int,
donde el valor de la casilla 0 es 5, el de la casilla 1 es 3, el de la 2 es 6, el de
la 3 es 10 y el de la casilla 4 es 7. La línea anterior es equivalente al código
siguiente:
Como hemos dicho varias veces, un array en Java es un objeto de una clase
interna de Java. Algunos ejemplos de clase interna que Java utiliza para ins-
tanciar el objeto para los arrays de tipo primitivo sería (el símbolo [ indica el
número de dimensiones):
Tabla 12
int[] [I
double[] [D
double[][] [[D
short[] [S
byte[] [B
boolean[] [Z
Como hemos visto, para inicializar un array se debe indicar su tamaño (length)
para ubicarlo en memoria correctamente. Una vez creado en memoria, el ta-
maño no puede ser modificado, es decir, es fijo. Este es uno de los mayores
inconvenientes que tienen los arrays.
(10)
Como un array es un objeto de una clase interna de Java, este pone a dispo- Más adelante veremos qué es
un atributo final.
sición de los programadores atributos públicos, como, por ejemplo, length.
Este atributo de solo lectura (es final)10 permite saber la longitud del array.
© FUOC • PID_00273929 69 Guía de Java
El valor que puede tomar el índice que se le pasa a un array va de 0 hasta el ta-
maño de length-1. En caso de usar un valor incorrecto para acceder a un ele-
mento del array –por ejemplo, un entero negativo o uno mayor a length-1–,
Java lanzará una excepción de tipo ArrayIndexOutOfBoundException, es
decir, Java avisará de que se ha producido un error.
Para los tipos primitivos, la clase Arrays ofrece el método sort el cual ordena
los elementos del array en orden ascendente, es decir, de menor a mayor.
© FUOC • PID_00273929 71 Guía de Java
Después viene el listado de parámetros que recibe (puede ser vacío). Para cada
parámetro indicaremos su tipo y su nombre (en lower camel case).
Finalmente, dentro de las llaves {} se escribe el código del método. Para de-
volver un valor (ya sea de tipo primitivo o referencia) se debe usar la palabra
reservada return. Cuando un método encuentra un return, la ejecución del
método finaliza devolviendo el control al punto donde el método ha sido lla-
mado/invocado. En los métodos con tipo de devolución void podemos no
escribir return o escribirlo. Lo más habitual es no escribir return.
Es muy habitual (seguro que alguna vez lo habéis hecho) encontrarse con có-
digo en el que se utilizan nombres diferentes para acciones similares sobre ti-
pos de parámetros diferentes. Por ejemplo:
Es importante darse cuenta de que Java mira las firmas de los métodos, es decir,
no considera el tipo de retorno (porque en Java estrictamente no es parte de la
firma). Así pues, la siguiente sobrecarga sería incorrecta y el compilador daría
un error:
Para el compilador de Java los dos métodos anteriores son el mismo método
porque tienen la misma firma:
Así pues, con el paso por valor no podemos modificar, desde el método, el
valor del elemento (es decir, variable) que ha sido pasado como argumento
al método.
Desde JDK 1.5 es posible definir un método que no tenga un número fijo de
parámetros. Es decir, unas veces recibe dos argumentos, otras veces tres, etc.
Hasta JDK 1.5 para simular esto lo que se hacía era pasar un array (fijaos que
en los parámetros no se indica el tamaño). Sin embargo, esto obliga a hacer
ciertas tareas adicionales y el código no expresa lo que realmente se quiere
indicar: la variabilidad de argumentos.
Para utilizar parámetros variables existe una restricción: solo el último pará-
metro puede ser definido como variable, es decir, con los tres puntos (...).
Los tres puntos indican que los argumentos correspondientes al último pará-
metro pueden ser pasados como un array o una secuencia de argumentos se-
parados por comas. Sea cual sea el formato, el compilador se encargará de em-
paquetar todos los varargs en un array. Así pues, el último parámetro lo ten-
dremos que tratar como un array para obtener los diferentes argumentos.
© FUOC • PID_00273929 76 Guía de Java
• Desde JDK 1.5 el parámetro del método main se puede escribir String[]
args o String ... args.
Cuando declaramos las variables, estas existen o no –es decir, pueden ser usa-
das o no– en un punto de un programa dependiendo de dónde hayan sido
declaradas dichas variables. Esa parte, área o región del programa en la que la
variable puede ser utilizada recibe el nombre en inglés de scope (en español,
ámbito, alcance o, incluso, visibilidad de una variable).
Una práctica muy común es crear en los bloques de iteración de tipo for
variables locales cuyo scope sea el cuerpo del propio bloque for. Veamos un
ejemplo:
Tened en cuenta que el scope de un bloque afecta al scope de los bloques que
tenga anidados. Así pues, si declaramos una variable en un bloque que tiene
anidado un segundo bloque, este segundo bloque no podrá declarar una va-
riable llamada exactamente igual, puesto que las variables del primer bloque
existen dentro del segundo bloque.
En este apartado vamos a ver cuál es la sintaxis mínima para declarar una clase
así como los elementos esenciales que definen toda clase: los miembros de la
clase (es decir, atributos y métodos) y constructores.
Para definir una clase en Java debemos tener presente que su esqueleto mínimo
es el siguiente:
• Nombre escrito en camel case, es decir, todas las palabras que componen
el nombre con la primera inicial en mayúscula, por ejemplo: MemberVip,
Ball, Person, Player, BankAccount, etc.
Como ya sabéis, las clases tienen miembros. Hay dos tipos de miembros de
la clase: los atributos y los métodos. Así pues, hay que definirlos. Lo más ha-
bitual es definir primero los atributos y después los métodos (y en medio, los
constructores). Es decir:
Atributos
Los atributos se definen igual que las variables (incluso respetan las conven- Nomenclatura para los
ciones usadas en las variables para los nombres), pero indicando además su atributos
modificador de acceso. De hecho, podéis pensar en ellos como variables glo- Además del término atributo
bales dentro de la clase. Es decir, todos los atributos de una clase, independien- (attribute), también se emplea
el término campo (field).
temente del modificador de acceso que se les asignen, son visibles en cualquier
parte de la clase en la que están definidos. La única diferencia con las variables
es, como hemos dicho, que a los atributos hay que asignarles un modificador
de acceso y a las variables no. Veamos algunos ejemplos:
Finalmente, cabe indicar que los atributos de una clase, a diferencia de las
variables, se crean dentro del objeto. Por consiguiente, a nivel de ubicación en
memoria, los atributos están en el heap (como los objetos), no en el stack.
Métodos
En segundo lugar, vamos a ver una situación que solo puede darse cuando
usamos atributos dentro de los métodos. Mirad el método setter siguiente:
el valor del parámetro ageNew. Sin embargo, lo ideal hubiera sido llamar al
parámetro igual que al atributo, para saber que están relacionados. Es decir, el
código «ideal» hubiera sido:
Es obvio que con esta solución de añadir una coletilla (en el ejemplo, "New")
no hay ninguna duda para el compilador ni para nosotros de qué es que. A
pesar de que podemos usar una solución como la anterior, lo más habitual y
mejor (como hemos comentado) es utilizar como nombres de los parámetros
de los métodos los mismos nombres que los de los atributos de la clase, en
este caso, age. Para resolver el conflicto de nombres, Java nos proporciona una
palabra reservada llamada this. Así pues, dada una versión simplificada del
método setAge como la siguiente:
El this hace de coletilla e indica que aquello que lleva this pertenece al ob-
jeto/clase y no es ni un parámetro ni una variable declarados dentro del méto-
do. Concretamente this hace referencia al objeto (o instancia) que es del tipo
© FUOC • PID_00273929 83 Guía de Java
de la clase. Por lo tanto, gracias a this, estamos diciendo que al atributo age
del objeto (o si os resulta más fácil de entender, de la clase) hay que asignarle
el valor del parámetro age (llamado ageNew en la versión anterior del código).
3.1.3. Constructor
(11)
El constructor por defecto anterior es correcto. Incluso, no haría falta por qué Recordad lo que comentamos
en el apartado «Protección de los
inicializar todos los atributos (fijaos que hemos eliminado la inicialización en
datos» del módulo «Abstracción y
la declaración de age... no tiene sentido duplicar). Ahora bien, es interesante encapsulación».
© FUOC • PID_00273929 84 Guía de Java
y una buena práctica utilizar los métodos setter y getter dentro de la propia
clase con tal de conseguir la máxima consistencia, estabilidad y robustez de
los atributos.11 Así pues, una mejor codificación del constructor anterior sería:
El hecho de usar los setters nos ayuda a crear un punto común de comproba-
ciones y asignaciones. Como recordaréis, dentro del método setAge se com-
prueba que la edad pasada como argumento no sea inferior a 0. Si lo es, im-
primimos un mensaje de error y asignamos el valor 0 al atributo age. En caso
contrario, asignamos el valor que nos facilitan.
Como hemos dicho, nuestras clases pueden tener tantos constructores como
necesitemos. Para ello, lo que tendremos que hacer es sobrecargar el construc-
tor. Veamos dos ejemplos de constructor con argumentos (o constructor pa-
rametrizado).
© FUOC • PID_00273929 85 Guía de Java
Si nos fijamos, el primer constructor con argumentos solo tiene tres paráme-
tros, mientras que el segundo tiene cinco (tantos como atributos tiene la cla-
se). ¿Os imagináis qué hubiera pasado si no llamáramos a los métodos setters
en los tres constructores? Pues que en los tres repetiríamos el mismo código
de comprobación que ahora tenemos centralizado en el método setAge. Una
vez más, se ve la importancia de llamar a los getters y setters también desde
dentro de la propia clase. Aun así, podéis ver que los tres constructores tienen
un código común: llamar a los setters de los atributos. ¿Por qué repetir líneas
de código idénticas? Java también se dio cuenta de que esto era muy común,
por lo que creó un par de maneras de reducir la repetición de código en los
constructores.
Para los casos en los que la inicialización de los atributos para los objetos (ins-
tancias) creados en una clase tienen valores conocidos a priori, entonces Java
proporciona lo que se conoce como bloque de inicialización (initializer block).
Imaginad, por ejemplo, que la edad inicial siempre sea 36 para todos los ob-
jetos creados para la clase Person. Entonces podríamos hacer:
En resumen:
• Si la clase en la que hay un bloque de inicialización hereda de otra clase, Herencia y clase padre
entonces primero se llama a los bloques de inicialización de instancia de la
El concepto de herencia y clase
clase padre, luego al constructor de la clase padre, después los bloques de padre lo veremos más adelante
inicialización de la clase para la que estamos creando el objeto y finalmen- tanto en los apuntes teóricos
como en esta guía.
te, el código del constructor que hemos usado para instanciar el objeto.
3.2. Objetos
Como al segundo paso se le llama instanciar, ahora entenderéis por qué a los
objetos también se les llama instancias.
Una vez hemos creado un objeto y este está referenciado por una variable/atri-
buto, lo más lógico es hacer algo con él, normalmente acceder a un atributo
o a un método. Para ello usaremos mensajes. La forma de un mensaje es:
Habréis visto que en los ejemplos anteriores los atributos, métodos y construc-
tores tenían la palabra public o private al inicio de su declaración. Como
bien sabéis, tanto public como private son dos modificadores de acceso,
cuya funcionalidad es determinar la accesibilidad/visibilidad de un elemento.
Cuando hablamos del uso de modificadores de acceso, nos estamos refiriendo
a que, si tenemos un objeto de una clase A dentro de otra clase B, según cómo
estén declarados los miembros de la clase A, se podrá acceder a estos directa-
mente mediante un mensaje o no desde B usando el objeto de la clase A.
© FUOC • PID_00273929 89 Guía de Java
Modificador package-private
Para asignar este modificador, no hay que escribir ningún modificador al declarar el ele-
mento. Es decir, cuando no se escribe ni public, ni protected ni private, Java en-
tiende que el modificador que se le quiere asignar al elemento es package-private. Así
pues, nunca hay que escribir la palabra compuesta package-private. Por este motivo,
a este modificador también se le conoce como no modifier o default.
Como podemos ver, al declarar, por ejemplo, una clase, solo podemos asignar-
le, o bien el modificador public, o bien el modificador package-private.
Sin embargo, si declaramos una clase dentro de otra clase, es decir, declaramos
una clase anidada (nested class), entonces dicha clase se comporta como un
miembro de la clase contenedora y, por consiguiente, podemos asignarle cual-
quiera de los cuatro modificadores de acceso que proporciona Java.
Llegados a este punto, cabe saber las diferencias entre los cuatro modificadores.
Es decir, qué implicaciones tienen cada uno de ellos. Vamos a comentarlos
suponiendo que dentro de una clase B tenemos un objeto de la clase A con un
miembro al que queremos acceder:
© FUOC • PID_00273929 90 Guía de Java
1)�public: indica que se puede acceder al elemento desde cualquier parte del
programa. Así pues, si dentro de la clase B queremos acceder a un miembro
público de un objeto de la clase A, lo podremos hacer desde B de la manera
siguiente:
2)� private: indica que solo se puede acceder al miembro desde dentro de
la propia clase que lo declara. Así pues, si intentamos acceder a un miembro
privado de la clase A usando un objeto de la clase A perteneciente a la clase B,
entonces obtendremos un error de compilación:
Modificador Desde el propio con- Desde el mis- Subclase en otro package El resto de
tenedor (p. ej., cla- mo package elementos
se, interfaz o enum)
public Sí Sí Sí Sí
package-private (sin Sí Sí No No
modificador)
private Sí No No No
Ahora imaginemos que la clase D es una subclase (concepto que veremos más
adelante) de la clase A y tiene un objeto de la clase A. Ambas clases pertenecen
al mismo package.
© FUOC • PID_00273929 93 Guía de Java
Finalmente, veamos una clase E que es subclase de A, pero ambas clases per-
tenecen a packages diferentes.
Algunos motivos por los que podemos utilizar el método finalize son: li-
berar recursos compartidos, cerrar conexiones (por ejemplo, bases de datos,
sockets, etc.), etc. Sin embargo, tened en cuenta que este método es llamado
automáticamente dentro del proceso conocido como garbage collection cuando
la JVM lo estima oportuno. Es decir, cuándo el método finalize es realmente
llamado es algo incierto. Así pues, no debemos depender de este método para
hacer algunas gestiones críticas. Lo que sí debemos tener presente es que el
método finalize no se llamará hasta que un objeto no tenga ninguna varia-
ble/atributo de tipo referencia apuntándole.
Como podéis ver, la gestión de la memoria en Java es mucho más sencilla que
en otros lenguajes como puede ser C++, esto también implica menos libertad
a la hora de «jugar» con la memoria del ordenador y poder optimizar procesos.
3.5. Packages
Figura 21
project
database
images
hd
user.png
sd
user.png
views
Si nos fijamos, tanto en las subcarpetas hd como sd, tenemos dos imágenes
llamadas exactamente igual: user.png. ¿Cómo podemos distinguirlas unívo-
camente? A través de su ruta (path): project/images/hd/user.png es dife-
rente a project/images/sd/user.png.
© FUOC • PID_00273929 96 Guía de Java
Pues bien, los packages en Java hacen exactamente esto: organizar nues-
tro código para que sea más fácil encontrar lo que queremos de manera
unívoca y, además, añadir información lógica a nuestro software.
Así pues, lo primero que debemos saber es que en un programa podemos tener
muchas clases escritas y algunas de ellas pueden incluso llamarse igual.
En la API de Java hay elementos llamados exactamente igual pero que perte-
necen a packages distintos. Por ejemplo, List existe tanto en java.util co-
mo en java.awt. En este caso concreto, además, se da la circunstancia de que
List es una interfaz en java.util, mientras que en java.awt es una clase,
¡son dos cosas diferentes que se llaman igual! Además, el package java.awt
incluye un conjunto de clases para crear interfaces de usuario, mientras que
java.util contiene un conjunto de elementos relacionados con colecciones
(es decir, estructuras de datos): listas, colas, pilas, tablas de hash, etc.
Para decirle al compilador de Java qué List usar en nuestro programa, debe-
mos indicarlo haciendo un import. Por ejemplo:
Crear un package en un IDE como Eclipse es tan sencillo como hacer botón
derecho con el ratón en el directorio src y escoger la opción New -> Package.
Todos los elementos (clases, interfaces y enumeraciones) que pertenecen a un
package deben tener como primera�línea�de�código la sentencia siguiente:
Así pues, imaginemos que tenemos dos clases que queremos que pertenezcan
al package mypackage. El código sería:
hecho, un IDE como Eclipse nos crea un package default cuando creamos
un proyecto Java nuevo con la finalidad de tener organizado nuestro código
desde el primer momento.
Por ejemplo, imaginemos que los programadores de las sedes de Barcelona y Madrid de
la UOC han creado el package assessment. Esto significaría que ambos equipos tendrían
edu.uoc.assessment pero con contenido (es decir, clases, interfaces, enumeraciones,
etc.) totalmente distintos (podría haber, por ejemplo, una clase llamada igual pero cuya
funcionalidad fuera diferente). Esta situación generaría conflictos si ambos packages se
quieren usar/integrar en un mismo programa. Por lo tanto, hay que buscar alguna ma-
nera de desambiguar. Cómo se desambigua lo decide en estos casos la propia compañía.
Una posibilidad, en este caso, sería añadir la sede: edu.uoc.barcelona.assessment
y edu.uoc.madrid.assessment. Con este pequeño cambio ahora sí que no hay ambi-
güedad posible.
© FUOC • PID_00273929 99 Guía de Java
eimt.int
uoc.8edu
uoc-eimt.edu
(12)
Vamos a ver cómo usar un elemento (clase, interfaz o enumeración) incluido En inglés, package members.
dentro de un package. A los elementos incluidos en un package se les llama
miembros del paquete.12 Hay tres formas de indicar que queremos usar un ele-
mento de un paquete.
1)� Importar� solo� el� elemento� a� usar. Esta manera ya la hemos visto en el
apartado «Modificadores de acceso». Recordemos que consiste en hacer un
import del elemento dentro del código del elemento donde lo queremos usar.
Como ya habéis leído en los apuntes teóricos, existe la keyword static que
puede ser aplicada a un atributo, método o clase anidada.
Cualquier objeto puede acceder y modificar el valor del atributo estático, pero
este atributo también puede ser accedido desde objetos de otras clases, si su
modificador de acceso lo permite, sin necesidad de crear un objeto de la clase
a la que pertenece el atributo estático. Así pues, si tenemos:
Para los casos en los que queremos inicializar atributos estáticos, estos pueden Ved también
ser inicializados en la declaración del propio atributo o en un bloque de ini-
El bloque de inicialización se
cialización estático (similar al bloque de inicialización). trató en el apartado «Cons-
tructor» de la presente guía.
Java permite usar la keyword static con métodos. Igual que los atributos,
los métodos estáticos pertenecen a la clase, no a la instancia. Así pues, estos
pueden ser llamados mediante un objeto o mediante el nombre de la clase
(esta forma es la recomendada).
¿Os habéis fijado que el método especial main es estático? Este se llama ha-
ciendo java NombreClase sin instanciar ningún objeto de la clase.
Es importante saber que existe una limitación al usar este tipo de métodos.
En Java no podemos declarar una clase de nivel superior con el modificador Ved también
static. Solo las clases anidadas pueden ser static.
Para saber más sobre las clases
anidadas (y las static), re-
3.7. Modificador abstract comendamos leer el apartado
«Clases anidadas» de esta guía.
En Java podemos usar la keyword abstract tanto para clases como para mé-
todos, pero no para atributos.
En Java, cuando una clase es declarada como abstracta significa que dicha clase
no puede ser instanciada. Es decir, no podemos crear objetos de esa clase.
© FUOC • PID_00273929 106 Guía de Java
En Java existe la keyword final que puede ser aplicada tanto a una clase, un
método como a un atributo.
De los apuntes teóricos sabemos que una clase final es aquella de la que no
se puede heredar.
Por los apuntes teóricos sabemos que un atributo declarado como final ac-
túa como constante. Por lo tanto, solo le podemos asignar un valor una vez
en todo el programa. Así pues, una vez asignando un valor, ya no podemos
modificarlo.
Es muy habitual combinar los modificadores static y final para crear cons-
tantes:
© FUOC • PID_00273929 108 Guía de Java
3.9. Herencia
En Java solo existe la herencia simple: una clase solo puede heredar directa-
mente de otra clase. Es decir, la clase C no puede heredar directamente de A y
B, a la vez. Sin embargo, existe la transitividad. O sea, si una clase B hereda de
una clase A, y una clase C hereda de B, entonces la clase C hereda de B y hereda
también todo aquello que haya heredado B de A.
Figura 22
En este punto cabe decir que en Java todas las clases así como los arrays here- Enlace recomendado
dan implícitamente de una superclase llamada Object definida por el propio
En el enlace siguiente po-
lenguaje Java. Es decir, queramos o no, todas las clases y arrays heredan de déis ver cómo se define
Object. la clase Object: https://
docs.oracle.com/en/java/ja-
vase/13/docs/api/java.base/
java/lang/Object.html.
Esta clase define un conjunto de métodos, entre los que destacan: clone,
equals y toString.
Constructores y herencia
3.9.3. ¿Qué hereda una subclase?
Los constructores no son con-
siderados miembros de una
Es clave saber qué hereda la clase B de la clase A. La respuesta es: todos los clase, porque, entre otras co-
sas, no se heredan.
miembros. ¿Todos los miembros? Sí, otra cuestión es si la clase B tiene acceso a
todos los miembros heredados de A como si estos se hubieran declarado en la
propia clase B. Sobre esto último, la respuesta es que una subclase solo tiene
acceso directo a los miembros declarados como public o protected en la
superclase. En el caso de los miembros package-private solo tiene acceso
directo si la superclase y la subclase pertenecen al mismo package.
5) Desde dentro de la subclase Dog, además de tener acceso directo a los mé-
todos setOwner, getOwner, setBreed, getBreed, speak y toString, tam-
bién se tiene acceso directo a los métodos de la superclase Animal declarados
como public o protected. Es decir: getName, setName, setAge, getAge y
speak. Fijaos que no tiene acceso directo a los métodos getNumberOfLegs y
setNumberOfLegs, ya que han sido declarados private en la clase Animal.
Así pues, en el código de la clase Dog no podemos llamar directamente a los
métodos getNumberOfLegs y setNumberOfLegs. En cambio, dentro de la
clase Dog, podremos acceder directamente al atributo vegetarian si Animal
y Dog están en el mismo package.
10) El último método que encontramos es toString. Como vemos, este mé-
todo está sobrescrito, ¿pero de qué superclase lo ha heredado Dog si en Ani-
mal no está? Pues bien, como hemos dicho previamente, todas las clases en
Java heredan implícitamente de la superclase Object definida por el propio
lenguaje Java. Esta clase Object tiene definidos, entre otros métodos, el mé-
todo toString (que ya habíamos visto anteriormente en esta guía). El méto-
do toString se utiliza para dar información sobre el objeto. Su código por
defecto, es decir, lo que codifica la clase Object, es devolver un String con
una representación interna del objeto e información de la referencia del obje-
to (es decir, la posición de memoria que ocupa en el heap).
Vemos que no podemos acceder al atributo name directamente desde Dog por-
que este atributo fue declarado private en la superclase. Así pues, la única
manera de acceder a él es indirectamente usando un método declarado en la
superclase como public o protected (o package-private si Animal y Dog
están en el mismo package) que nos dé acceso a dicho atributo. En este caso
accedemos a name mediante getName, que es public.
Ahora veamos otro código Java que utiliza las clases Animal y Dog:
© FUOC • PID_00273929 114 Guía de Java
Una duda que suele surgir con las clases abstractas es si tiene sentido declarar-
les un constructor si una clase abstracta, por definición, no puede ser instan-
ciada. Pues bien, una vez visto cómo se relaciona una subclase con su super-
clase y que en el constructor de la subclase se debe llamar a algún constructor
de la superclase, la respuesta está clara: sí, tiene sentido declarar un construc-
tor en una clase abstracta si esta será superclase de alguna clase. El construc-
© FUOC • PID_00273929 115 Guía de Java
tor de la superclase abstract no tiene por qué ser public, pero puede ser
protected para que así tengan acceso directo los constructores de la subclase
mediante el método especial super.
Lo explicado hasta ahora para los métodos también ocurre con los atributos
estáticos. Es decir, en la subclase podemos declarar un atributo llamado exac-
tamente igual que uno de la superclase. En tal caso, lo que hacemos es ocultar
dicho atributo y podremos llamar a uno u a otro en función de si lo llamamos
usando la superclase o la subclase.
Como ya vimos, este modificador tiene una afectación directa con la herencia.
Recordemos que si una clase es final entonces no puede ser heredada, por lo
tanto, no podremos definir subclases a partir de la clase final.
3.10. Interfaces
Como ya sabemos, las interfaces son contratos que indican qué métodos de-
ben ser codificados en las clases que las implementan. Las interfaces no pro-
porcionan el código de los métodos, solo sus firmas.
Una interfaz no puede implementar otra interfaz, puesto que no podría pro-
porcionarle su código, pero sí puede heredar una o más interfaces.
Esta es una pregunta muy habitual. También cabe decir que la respuesta de-
pende de la versión de Java de la que estemos hablando, puesto que el con-
cepto de interfaz en Java ha ido evolucionando con el paso de los años. Eso sí,
lo que se introdujo en una versión se ha mantenido en las siguientes.
Desde Java SE 7, las interfaces solo permitían constantes (atributos que implí-
citamente son declarados public static final) y métodos abstractos.
(14)
A partir de Java 8, las interfaces fueron capaces de incluir también métodos En inglés, default methods.
14
estáticos y métodos por defecto. La inclusión de estos dos tipos de métodos
nos permite escribir código dentro de un método.
© FUOC • PID_00273929 118 Guía de Java
• Los métodos privados de una interfaz solo pueden usarse dentro de la in-
terfaz en la que han sido definidos.
• Los métodos privados static de una interfaz pueden usarse tanto en mé-
todos estáticos como no estáticos de la interfaz.
Resumiendo:
Métodos�private Java 9
3.11. Polimorfismo
Veamos esto con código (suponed que todas las clases pertenecen al mismo
package):
• La línea 4 imprime "Woof!!", puesto que tanto el tipo estático como di-
námico de la variable dog1 es Dog.
3.12. Excepciones
3.12.1. Concepto
Como podemos ver, gracias al bloque try-catch, Java nos permite separar
la lógica del programa de la gestión de las excepciones. El flujo normal del
programa está dentro del bloque try, mientras que cada excepción se trata en
el bloque catch correspondiente. Esta separación realmente facilita mucho la
lectura y entendimiento del código.
(15)
Cuando ocurre una excepción, el programa crea un objeto llamado exception En inglés, throwing an exception.
object que incluye información valiosa sobre el error, como por ejemplo, el
tipo de excepción, dónde ha sucedido dicho error, etc. El exception object es
lanzado a la JVM, que lo gestiona. Este proceso de lanzar exception objects es
lo que se conoce como lanzar una excepción.15 Veamos cómo sería el código
anterior usando excepciones.
1) Cuando dentro del método se llama a otro método que lanza una excep-
ción (es decir, cuando la signatura de este método que se llama tiene throws
Exception) y dentro de él se lanza la excepción.
orden de ejecución del programa. Este orden de ejecución está formado por
una pila –llamada call stack– con los métodos que han sido invocados hasta
que ha ocurrido la excepción.
Figura 23
Figura 24
Vamos a seguir con el ejemplo del apartado anterior, pero ligeramente modi-
ficado para introducir un try-catch:
Figura 25
En Java hay dos categorías de excepciones: las checked y las unchecked. Así pues,
vamos a definir ambos tipos:
¿Por qué unas excepciones son checked y otras unchecked? Java considera que
las unchecked son todas aquellas excepciones en las que el programador poco
puede hacer para lograr que el flujo de ejecución del programa no se vea al-
terado. En otras palabras, Java considera que de las excepciones unchecked es
difícil recuperarse, es decir, encontrar una solución que permita continuar la
ejecución del programa como si no se hubiera dado dicha excepción.
Por ejemplo, cuando dividimos un número entre cero se produce una excepción llamada
ArithmeticException que es de tipo unchecked. Poco podemos hacer, a priori, si hay
una división entre cero, seguramente se debe a una mala lógica del programa ya que antes
de dividir dos números deberíamos comprobar que el denominador sea distinto a cero.
Lo mismo ocurre cuando intentamos acceder a una casilla que no existe de un array. Por
ejemplo, si queremos acceder a la casilla/posición 24 de un array de 5 casillas, la JVM
durante la ejecución del programa (no en tiempo de compilación) lanzará una excepción
de tipo ArrayIndexOutOfBoundException. En este caso, poco podemos hacer. Nueva-
mente se debe a un error en nuestro código: o bien hemos usamos un índice cuyo valor
está mal asignado (un error clásico cuando se empieza a aprender a usar los bloques de
iteración), o bien no hemos comprobado que el índice que se usa para acceder a un ele-
mento del array esté dentro del rango correcto del array.
Figura 26
Todas las clases en un tono más oscuro son checked, lo que quiere decir que
se deben capturar o propagar obligatoriamente en nuestro código. ¿Por qué?
Porque para este tipo de errores podemos gestionar qué hacer en el programa
para solucionar la aparición de dicha excepción. Por ejemplo, realizar una ac-
ción alternativa, hacer un rollback (deshacer operaciones que se han hecho),
salir del programa avisando amablemente al usuario final (no repentinamen-
te) y cerrando ficheros, sockets, conexiones a bases de datos para dejarlos en
un estado consistente, etc.
En cambio, todas las clases en tono claro son unchecked y no es necesario captu-
rarlas ni propagarlas. Como podéis ver, son errores (de hecho heredan/cuelgan
de la clase Error, excepto RuntimeException y sus subclases) que ocurren
© FUOC • PID_00273929 131 Guía de Java
con poca frecuencia y que poco se puede hacer como programador para solu-
cionar el problema. No obstante, como programadores podemos capturarlas e
intentar tratarlas, aunque, como ya hemos comentado, Java no nos obliga.
Llegados a este punto cabe destacar que, como hemos visto anteriormente,
podemos tener tantos catch como excepciones queramos tratar de manera
diferente, ya que dentro de un bloque try se pueden dar diferentes tipos de
excepciones. Es más, un único método puede lanzar más de una excepción.
Por ejemplo:
Ahora observad el siguiente código que parece similar al que llama al método
printStackTrace por separado, pero su comportamiento no es exactamente
idéntico:
Cualquier objeto que sea de una clase que hereda de Throwable, es decir, to-
das las que usamos para propagar excepciones, tiene métodos que nos propor-
cionan información adicional sobre la excepción. Veamos los tres más intere-
santes:
2)� toString(). Devuelve un String con una breve descripción del objeto.
Básicamente el nombre de la clase de tipo Throwable seguido del símbolo :
y el texto de getMessage().
3.13. Enumeraciones
(16)
Si habéis codificado en C/C++ u otros lenguajes, habréis usado enumeracio- En inglés, enumeration.
16
nes o simplemente enum. Como ya sabréis, los enum son un tipo definido por
el propio programador. Normalmente se utilizan cuando queremos restringir
los posibles valores que puede tomar una variable/atributo o estos valores con-
forman un dominio concreto y conocido. Por ejemplo, días de la semana, me-
ses del año, planetas del sistema solar, etc.
© FUOC • PID_00273929 135 Guía de Java
Por ejemplo:
Como ya hemos dicho, Java trata los enumeration como clases. Eso sí, son cla-
ses especiales que proporcionan una implementación type-safe (seguridad de
tipos) de datos constantes. Es decir, al declarar una variable del tipo del enu-
meration, solo se le puede asignar los valores del enumeration, ningún otro.
(17)
3)�Fragilidad.17 Debido a que las constantes de un dominio (que simularían un En inglés, brittleness.
4)�Print's sin información valiosa. Porque las constantes solo son enteros,
al hacer un println este imprime los valores enteros: 0, 1 y 2. Esto no nos
da información sobre lo que representan. En cambio, el enum imprime STONE,
PAPER y SCISSORS.
Los enum pueden usarse con switch como si de un entero u otro tipo básico
se tratara.
© FUOC • PID_00273929 137 Guía de Java
Además el compilador crea una instancia de la clase para cada constante defi-
nida dentro del enum. Estas instancias son public static final. Es decir,
no se pueden modificar (final) y se accede a ellas anteponiendo el nombre
del enum. Por ejemplo: Day.MONDAY. Asimismo, no se puede instanciar nin-
gún objeto de tipo del enum.
Cuando un enum es definido, Java crea una clase que hereda de la clase Enum.
Por este motivo, ningún enum puede heredar de otra clase o enum. Sin embar-
go, podemos hacer que un enum implemente una o más interfaces.
Veamos su uso:
Además los enum tienen un método muy interesante llamado values() que
devuelve un array con los valores del enum.
© FUOC • PID_00273929 138 Guía de Java
Toda variable de tipo enum es una referencia a una zona de memoria, igual que
una clase, una interfaz o un array. Esta variable es implícitamente final, ya
que sus valores no pueden modificarse. Sin embargo, el enum, al ser como «una
clase especial», puede definir un constructor y miembros del enum (es decir,
atributos y métodos). Esto es precisamente lo que hace al enum de Java más
poderoso en comparación con el enum de otros lenguajes de programación.
Veamos un ejemplo:
3.14. Generics
3.14.1. Concepto
Así pues, por ejemplo la clase ArrayList<E> es un tipo genérico (más común-
mente llamado tipo parametrizado) que tiene un parámetro de tipo llamado E.
Cuando usamos ArrayList<Person> o ArrayList<Animal>, a estos se les
llama tipos parametrizados, donde Person y Animal son los argumentos de
tipo, respectivamente. Veremos esto con más calma a continuación.
Con Java podemos crear nuestras propias clases genéricas. Estas clases tienen
uno o más parámetros de tipo, que son tipos genéricos. Su sintaxis más simple
es la siguiente:
© FUOC • PID_00273929 140 Guía de Java
Llegados a este punto, aunque solo hemos hablado de clases genéricas, vamos
ver qué ventajas tiene el uso de generics:
Veamos ahora la ventaja de usar una clase genérica en vez de hacer uso de la
clase Object para la misma finalidad.
Los dos códigos anteriores son dos maneras de codificar la clase A. En el pri-
mero usamos la clase raíz Object de la que heredan todas las clases. En cam-
bio, en el segundo código hemos convertido la clase A en una clase genérica.
A priori puede parecer que son el «mismo» código, pero hay diferencias signi-
ficativas en el uso. En el código anterior, de hecho, no hay muchas ventajas
de usar uno u otro código. La mayor ventaja es que la clase del primero puede
recibir cualquier clase y devolver cualquier clase porque trabajamos siempre
con Object. En cambio, el segundo código permite definir con qué clase tra-
baja la clase genérica y, entonces, el compilador puede detectar errores y que
la clase A no sea usada con clases que no hemos definido al instanciarla.
Por otro lado, ¿qué pasaría si tuviéramos un método en la clase A que hiciera
uso del atributo obj para llamar a uno de sus métodos?
© FUOC • PID_00273929 142 Guía de Java
De manera similar a una clase, también podemos definir nuestras propias in-
terfaces genéricas.
Como podemos ver, antes de la declaración del tipo de retorno debemos poner
los parámetros de tipo dentro de <>.
Para llamar a este método que encuentra un elemento dado en un array, po-
demos hacer:
En primer lugar, es importante resaltar que podemos tener uno o más pará-
metros de tipo. Por ejemplo:
Asimismo, cabe indicar que, por convenio, el nombre del parámetro de tipo
suele ser un único carácter escrito en mayúsculas. Los nombres más habituales
son:
© FUOC • PID_00273929 144 Guía de Java
• S, U, V, etc.: para segundo, tercer, cuarto tipo, etc. En una clase genérica
podemos tener más de un parámetro de tipo.
• V: valor.
Por otro lado, los tipos que pueden utilizarse como parámetros de tipo solo
pueden ser clases o interfaces. Así pues, si queremos usar tipos primitivos, no
podemos. La solución será usar las correspondientes clases�wrapper (envolto-
rio). Estas son:
3.14.7. Subtipos
Con el código anterior estamos creando una interfaz llamada MyList que
hereda de la interfaz Java llamada List (hablaremos de ella en el siguien-
te apartado). Si List fuera List<Integer>, entonces nuestra interfaz My-
List podría ser MyList<Integer,Object>, MyList<Integer,Integer>,
MyList<Integer,Double>, MyList<Integer,String>, etc.
(18)
Con los generics podemos usar el comodín18 ?. Este comodín representa un En inglés, wildcard.
tipo desconocido o, dicho de otro modo, cualquier tipo. Este comodín puede
ser usado como parámetro de tipo, tipo de un atributo o incluso como tipo del
retorno de un método (aunque esto último no es aconsejable). Sin embargo, ?
no puede ser usado para llamar a métodos genéricos ni para instanciar objetos
de clases genéricas.
Hasta ahora hemos visto que los parámetros de tipo podían ser sustituidos
por cualquier clase o interfaz. Sin embargo, esto a veces no es lo deseado y
queremos limitar los tipos que se pueden pasar a un parámetro de tipo.
Un ejemplo claro es si queremos que los tipos que se reciban sean numéricos.
Es decir, Integer, Float o Double, pero no String ni otro tipo. Si tuviéra-
mos el siguiente código, este no funcionaría:
© FUOC • PID_00273929 146 Guía de Java
Con el código anterior el compilador nos daría error porque no sabe que nues-
tra intención es usar la clase Maths solo con tipos numéricos y que, por con-
siguiente, sumar dos elementos es correcto. Así pues, debemos indicarle que
solo usaremos clases que sean numéricas. Es decir, limitaremos los tipos que
puede aceptar el parámetro de tipo T. Esto lo haremos con los bounded types
(es decir, tipos limitados). El código anterior usando bounded types sería:
Como podemos apreciar, hemos usado la palabra reservada extends para de-
cir que todos los tipos que reciba T deben heredar de la clase Number. En es-
te caso, tanto Integer como Float y Double heredan de esta clase. Ahora
podremos hacer:
Otro ejemplo donde indicamos que los tipos pasados al parámetro de tipo
cumplen una condición es el siguiente:
El código anterior nos está diciendo que lo que pongamos en T debe ser o
Number o una subclase de Number (es decir, una clase que herede directa o
indirectamente de Number).
Y este:
Existe otro limitador llamado super. Con él estamos diciendo que el tipo que
se utiliza (lado izquierdo) debe ser una superclase o superinterfaz del elemento
indicado a la derecha o el propio elemento. Es decir:
Nos está diciendo que lo que usemos (?) debe ser o Animal o una superclase
directa o indirecta de Animal.
• Usaremos extends cuando solo queramos consultar los valores (es decir,
acceso de lectura).
3.15. Colecciones
Figura 27
Como se puede ver en el diagrama de la figura 27, hay tres interfaces (Set,
List y Queue) que heredan de la interfaz Collection. Las clases que imple-
mentan estas tres interfaces permiten añadir un número «infinito» de elemen-
tos (es decir, el asterisco de los diagramas de clases UML).
Cada una de las clases que implementan Set se comporta como una
estructura de datos que guarda un conjunto de objetos únicos. Es decir,
las clases que implementan Set no permiten elementos duplicados.
1)� Ordenación: HashSet guarda los elementos sin seguir un orden lógico,
mientras que LinkedHashSet guarda los elementos en el orden de inserción
de los mismos y TreeSet los guarda siguiendo, o bien las indicaciones que
codificamos en el método compareTo() (para usar este método, la clase del
objeto que guardamos debe implementar la interfaz Comparable y sobrescri-
bir dicho método), o bien mediante un objeto de una clase que implemente
© FUOC • PID_00273929 151 Guía de Java
En líneas generales, de las tres clases se suele usar HashSet, puesto que es
mejor en rendimiento, a no ser que realmente se necesite guardar elementos
únicos (es decir, sin duplicados) en un orden específico. En este caso, se utiliza
LinkedHashSet o TreeSet.
Cómo podéis ver en el diagrama UML anterior (figura 27), otra rama es la que
empieza con la interfaz List. Como se puede apreciar, hay tres clases (en reali-
dad hay más) que implementan List: ArrayList, Vector y LinkedList.
Estas clases, las más usadas de List, tienen en común que se comportan como
una lista (de ahí que implementen la interfaz List).
Las tres clases permiten insertar objetos duplicados y, además, los obje-
tos insertados están ordenados por índice.
Por otro lado, si se hacen más consultas de acceso aleatorio (es decir, get)
que inserciones, es preferible usar ArrayList, ya que LinkedList recorre
toda la lista hasta alcanzar la posición/índice indicado. El borrado, sobre todo
si se realiza al comienzo o en el medio de la lista, tiene un mejor coste en
LinkedList, puesto que si se elimina un objeto del medio, no se deben mover
todos los objetos posteriores como sucedería con la ArrayList.
Finalmente llegamos a la rama de la interfaz Queue (en español, cola), cuyas (19)
Acrónimo de First-In, First-Out.
19
clases siguen la filosofía FIFO. De hecho, como ya se mencionó, LinkedList
implementa Queue además de la interfaz List, teniendo un comportamiento
híbrido. La otra clase que implementa Queue es PriorityQueue, que se trata
de una cola con prioridad.
(20)
Lo que hemos visto hasta ahora son estructuras que, más o menos, conocéis, Acrónimo de Last-In, First-Out.
al menos, conceptualmente: lista, cola y conjunto (set). Nos ha faltado ver la
pila, una estructura que también conocéis y que sigue la filosofía LIFO20 y que
en Java está codificada con la clase Stack que hereda de la clase Vector.
(21)
Si analizamos el código JavaScript anterior, lo que hacemos es crear un array En inglés, key-value pair.
(sin longitud, esto lo permite hacer JavaScript) y a cada casilla en vez de utilizar
los índices 0, 1, 2 y 3, usamos una clave, en este caso de tipo textual (String).
Ahora ya no usamos 0, 1, 2 y 3 para acceder a las casillas del array, sino que
usamos claves. Así pues, en la casilla con clave igual a "9781412902243"
(que no quiere decir que sea ni la casilla 0 ni la casilla ubicada en la posición
9781412902243 del array) está el valor "Terracota Warriors". Por lo tan-
to, estamos usando una estructura basada en un par clave-valor.21 Seguramente
os estaréis preguntando: «¿para qué puede ser útil un array asociativo?» Pues,
© FUOC • PID_00273929 153 Guía de Java
por ejemplo, imaginemos que la clave es el nombre corto utilizado para cada
uno de los equipos (Team) que tenemos en un juego de fútbol y dentro de
la casilla correspondiente guardamos un objeto de la clase Team. Gracias al
array asociativo seremos capaces de acceder de manera directa a todo el objeto
Team sabiendo su nombre corto único. Si usamos otro tipo de estructura, por
ejemplo, una lista, para poder encontrar el ítem con identificador "Barça",
tendríamos que hacer una búsqueda por toda la estructura hasta encontrarlo.
Eso sí, quizás os lo estáis planteando, pero cada objeto guardado en el array
asociativo tiene que tener una clave�única, si no, solo podremos guardar uno
de los dos objetos como valor de aquella clave. Por ejemplo:
En Java las clases asociativas se modelan, principalmente, con las clases que
implementan la interfaz Map.
Figura 28
Hashtable es como HashMap pero sincronizado. Esto quiere decir que puede
usarse en programas con más de un hilo de ejecución. Obviamente, esta ca-
racterística le comporta un sobrecoste al programa.
3.15.4. Resumen
Con la explicación de las clases que implementan la interfaz Map, parece que
todo son ventajas y lo mejor es usar una estructura de datos de tipo Map, en
vez de tipos List o Set. Bien, pues evidentemente, cada clase tiene su uso.
Por ejemplo:
• Las clases procedentes de List, como por ejemplo ArrayList, son útiles
si necesitamos guardar el orden de los objetos que almacenamos.
• Además, las clases de tipo List consumen menos memoria, pues solo
guardan el objeto que insertamos, mientras las que son de tipo Map guar-
dan dos objetos: la clave y el valor.
• Las clases de tipo List pueden guardar duplicados de valor, mientras que
las de tipo Map solo pueden duplicar valores, no claves. Las clases de tipo
Set no permiten duplicados.
• Las clases de tipo List y Map permiten el valor null. Eso sí, las de tipo
Map solo permiten null una vez como clave.
• A diferencia de las clases que implementan List o Map, las clases que im-
plementan Set no tienen un método para acceder directamente a un ele-
mento guardado (conocido como acceso aleatorio), sino que se debe iterar
sobre toda la colección hasta llegar al elemento deseado.
Figura 29
© FUOC • PID_00273929 156 Guía de Java
4. Avanzado
1) Podemos asignar un texto con dobles comillas (String literal) a una variable
de tipo String sin necesidad de llamar al constructor para crear una instancia
de la clase String.
Así pues, un objeto String puede crearse como si fuera un tipo primitivo (por
ejemplo, como hace un entero: int a = 5;) o como un objeto:
3) Los String literals son almacenados como objetos en un lugar común llama-
do String common pool dentro del heap. Esto facilita la compartición y eficien-
cia del almacenaje. Por su parte, cuando el String es creado con la llamada
explícita al constructor, se crea un objeto (al que llamaremos String object para
distinguirlo del String literal) que se almacena directamente en el heap. Mirad
este código:
© FUOC • PID_00273929 157 Guía de Java
Figura 30
Así pues, si dos String literals tienen el mismo contenido, entonces apuntan y
comparten el mismo espacio de memoria en el String common pool. Por su parte,
cada uno de los String creado como objeto (es decir, con new) se comporta
como cualquier objeto de otra clase ocupando su propio espacio en el heap,
aunque el contenido de dos String objects sea el mismo.
4) Debido a que los String literals con idéntico contenido comparten la zona
de memoria en el String common pool, Java define String como inmutable. Es
decir, el contenido no puede ser modificado una vez ha sido creado. Por este
motivo, cuando llamamos a un método como toLowerCase(), este método
no modifica el String que le pasamos, sino que crea uno nuevo con todas
las letras en minúsculas. Esto se hace así para que String se comporte como
inmutable, pero dicho comportamiento puede crearnos problemas:
Para ver más clara la diferencia entre String literal y String object, veamos el
ejemplo siguiente:
© FUOC • PID_00273929 158 Guía de Java
La principal diferencia entre estas dos clases es que StringBuilder no es sin- (22)
En inglés, thread.
cronizada, mientras que StringBuffer sí lo es. Esto significa que podemos
usar StringBuffer en programas con más de un hilo de ejecución,22 es decir,
cuando estamos haciendo programación paralela. Por este motivo, lo más ha-
bitual es usar StringBuilder porque además su eficiencia, al no ser sincro-
nizado, es mejor que StringBuffer.
Como ambas clases son clases normales, entonces solo se pueden crear a partir
de un constructor, no podemos asignarles directamente un String literal. Tam-
poco se puede usar el operador +, sino los métodos append() o insert().
4.1.2. Resumen
La sentencia anterior (en SQL llamada query) pide a la base de datos que retorne
todos los usuarios de la tabla users cuyo apellido sea Anderson. En este caso
no sabemos si la base de datos internamente usará un for o un while o un
if o un switch o un array u otra estructura. Por así decirlo, nosotros pedimos
lo que queremos, el programa se «busca la vida» para satisfacernos.
Así pues, las interfaces funcionales se pueden usar para crear un tipo concreto
a partir de la definición de una expresión lambda.
Llegados a este punto puede parecer que muchas veces tengamos que crear
una interfaz funcional si queremos hacer algo complejo. Como hemos visto,
las expresiones lambda pueden trabajar con interfaces funcionales para lograr
la reutilización de su código a lo largo de nuestro programa, pero muchas ve-
ces usaremos una expresión lambda en un contexto concreto una sola vez.
De hecho, Java tiene muchos métodos en sus diferentes clases que aceptan
como parámetro una expresión lambda. Por ejemplo, ahora las clases que im-
plementan la interfaz Iterable tienen el método forEach. Algunas de estas
clases son ArrayList, LinkedList, HashSet, etc. Este método lo que hace es
recorrer (o iterar) una estructura de datos y para cada elemento de la estructura
ejecutar la operación (que será una expresión lambda) pasada por parámetros.
Así pues:
Finalmente, cabe decir que las expresiones lambda se están utilizando mayo- Enlace recomendado
ritariamente con la nueva API Stream que introdujo JDK 8. De manera resu-
En el enlace https://
mida, podemos decir que a través de la API Stream podemos trabajar sobre docs.oracle.com/en/java/ja-
colecciones como si estuviéramos realizando sentencias SQL (gracias a las ex- vase/13/docs/api/java.base/
java/util/stream/packa-
presiones lambda). Esto permite que trabajemos de una manera limpia y cla- ge-summary.html encontra-
réis ejemplos de cómo utili-
ra, evitando bucles y algoritmos que ralentizan los programas e incluso hacen
zar la API Stream.
que el código se torne inmanejable.
Existen tres partes que componen un Stream, que de manera general serían:
La expresión anterior filtra los empleados que cobran 1000 € o más y suma sus
cantidades. Así sabemos cuál es el coste salarial de los empleados que cobran
1000 € o más.
4.3. Módulos
Si habéis creado proyectos con Eclipse utilizando una versión igual o superior
a JDK 9, os habréis dado cuenta de que os pregunta si queréis crear un fichero
llamado module-info.java. El hecho es que JDK 9 introdujo el concepto de
módulo para mejorar aún más la encapsulación.
Figura 31
Para definir todo esto, los módulos usan descriptores que se escriben en un
fichero llamado module-info.java que está ubicado en la raíz del código
fuente del módulo. Fijaos que el nombre que tiene este fichero rompe la regla
de nomenclatura de Java, la cual no permite poner un guion como nombre de
fichero. De esta forma, las herramientas (java, javac, IDE, etc.) no lo con-
funden con una clase Java normal.
© FUOC • PID_00273929 163 Guía de Java
Dentro de las llaves podemos usar diferentes sentencias. Las dos más usadas
son:
1)�exports indica que las clases públicas del paquete exportado son visibles
para el resto del mundo.
Para este ejemplo solo las clases públicas que están dentro del paquete
edu.uoc.logging serán visibles fuera del módulo a. Así pues, las clases pú-
blicas de otro paquete del módulo a, por ejemplo, edu.uoc.classroom, no
serán visibles fuera del módulo.
Por lo tanto vemos que, gracias a los módulos, aun siendo pública una clase
en un paquete, esta no lo será para los elementos de fuera del módulo si no
se exporta explícitamente. Así pues, los módulos pueden ocultar visibilidad
pública, lo cual mejora la encapsulación en Java.
(23)
En primer lugar, vamos a definir qué es una clase anidada.23 En inglés, nested class.
Hay dos tipos de clases anidadas: las static nested classes y las inner classes. Vea-
mos cada una de ellas.
La clase anidada estática tiene acceso directo a todos los miembros estáticos de
la clase externa. Si desea acceder a miembros no estáticos, se deberá usar una
referencia a un objeto de la clase externa. Por este motivo, las clases anidadas
estáticas son poco utilizadas.
Una clase interna (inner class) es una clase anidada no estática. Su uso más ha-
bitual es proporcionar una clase que solo se utiliza dentro de su clase externa.
Se declara de la manera siguiente:
Para crear una instancia de la clase interna, primero se debe crear el objeto de
la clase externa:
© FUOC • PID_00273929 165 Guía de Java
Dentro de la categoría inner classes, existen dos variantes: las clases internas
locales y las clases internas anónimas. Dejamos en vuestras manos la profun-
dización en estas dos variantes.
4.5. Anotaciones
O incluir elementos:
Anotación Descripción
@SafeVarargs Solo se puede usar con métodos con varargs. Sirve para
que no se realicen operaciones inseguras sobre el pará-
metro varargs.
Para crear nuestra propia anotación debemos crear un fichero .java con el
nombre de la anotación y usar en él la sintaxis siguiente:
Un ejemplo:
© FUOC • PID_00273929 167 Guía de Java
• Los tipos de retorno de los métodos son tipos primitivos, String, Class,
enum, anotaciones y arrays de los tipos anteriores.
• Los métodos pueden tener un valor por defecto. Lo indicamos con la pa-
labra clave default.
Anotación Descripción
@Repeatable Indica que la anotación puede utilizarse más de una vez para un
mismo elemento.
Veamos un ejemplo:
Debemos fijarnos que cuando usamos una anotación, los elementos de esta
van ubicados dentro de paréntesis.
Existe una sobrecarga de este método que permite indicarle el mensaje de error
a mostrar si se lanza la excepción de tipo NullPointerException.
5. Extras
5.1. Javadoc
Para las clases se suele hacer una breve descripción de esta y usar las etiquetas
@author y @version. La primera indica el nombre del autor que ha realizado
la clase (si hay más de uno, se pueden usar tantas etiquetas @author como
autores). Por su parte, la segunda etiqueta informa de la versión de la clase
(una clase puede verse modificada en el tiempo y, en un momento concreto,
se puede considerar que es una nueva versión).
Etiqueta Descripción
@throws Descripción de la excepción que puede propagar. Habrá una etiqueta throws
por cada tipo de excepción.
@see Enlace a la documentación de otra clase, ya sea de Java, del mismo proyecto o
de otro.
@since Indica desde cuándo existe este método. Puede ser cualquier texto, pero nor-
malmente es el número de versión de la clase.
© FUOC • PID_00273929 170 Guía de Java
Etiqueta Descripción
@depreca- Marca el método como obsoleto. Solo se mantiene por compatibilidad y, por lo
ted tanto, aún se puede usar. Es posible que en futuras versiones de la clase el méto-
do desaparezca definitivamente y no se pueda usar.
Una vez escritos los comentarios, existen varias maneras de crear la documen-
tación. Vamos a ver dos de ellas.
En cualquiera de las dos versiones podemos indicar que nos genere toda la
documentación en un directorio específico (si no existe, lo crea) mediante el
comando -d nombreDirectorio:
Finalmente, cabe indicar que cuando se llama al comando javadoc por línea
de comandos, solo genera la documentación para los atributos y métodos que
son públicos y protegidos. Si queremos que incluya también los privados, se
lo debemos indicar de la manera siguiente:
Dar información sobre los atributos y métodos privados es útil si estamos com-
partiendo información con alguien que debe programar aquella clase por den-
tro. En caso contrario, ¡no se deben dar detalles de los atributos y métodos
privados! ¡Esta es la gracia de la encapsulación!
Para generar la documentación con javadoc en Eclipse, debemos seguir los Nota
pasos siguientes:
Es importante seleccionar el
proyecto, ya que si tenemos
1) Ir al menú superior de Eclipse y escoger «Project → Generate Javadoc...». seleccionada una clase, solo
nos hará el Javadoc de esa cla-
se.
Figura 32
© FUOC • PID_00273929 172 Guía de Java
Figura 33
Figura 34
Como podemos imaginar, Eclipse hace por nosotros las llamadas al comando
javadoc explicadas en el apartado «Generar javadoc por línea de comandos»
según sea la configuración que le hemos indicado.
Figura 35
5.2. JavaFX
5.2.1. Introducción
(24)
Las interfaces�gráficas�(GUI)24 son un elemento básico hoy en día. Java tie- Acrónimo de graphical user in-
terface, en inglés.
ne tres bibliotecas para hacer interfaces gráficas: AWT, Swing y JavaFX. Resu-
miendo mucho, se podría decir que Swing es más avanzado que AWT y que Ja-
vaFX ha llegado para ser el sustituto de Swing. Hasta hace relativamente poco,
Swing era la librería predominante, pero JavaFX está comenzando a ganarle
terreno. No obstante, en un programa podéis combinar componentes (es de-
cir, botón, campo de texto, desplegable, etc.) de ambas librerías, aunque no es
muy recomendable. Oracle no ha dado por obsoleto a Swing, pero parece ser
que no le va a incorporar mejoras en el futuro. Es por esto por lo que en esta
guía nos centraremos en JavaFX. Asimismo, con JDK 11 Oracle ha decidido
quitar del núcleo del JDK la librería JavaFX para que esta sea independiente y
tenga su propio ritmo de desarrollo, liberando así su evolución. Esto, eviden-
temente, conlleva algunos cambios, especialmente a la hora de configurar el
entorno de trabajo (por ejemplo, Eclipse).
En este apartado vamos explicar, paso a paso, cómo configurar JavaFX en Eclip-
se.
© FUOC • PID_00273929 175 Guía de Java
2) Como ahora JavaFX está fuera del núcleo de JDK, hay que ir a la siguiente
página web: https://gluonhq.com/products/javafx/.
Figura 36
Figura 37
Figura 38
10) Para que Eclipse, es decir JDK, detecte la librería JavaFX, hay que ir impor- Ved también
tando los paquetes que usemos de JavaFX en el fichero module-info.java. Si
Se ha hablado del fichero es-
no estamos usando este fichero para nada, lo más sencillo, entonces, es hacer pecial module-info.java
lo siguiente: vamos al fichero module-info.java y comentamos el código: en el apartado «Módulos» de
esta guía.
11) En este punto los errores relacionados con el uso de clases de la librería
JavaFX deberían desaparecer.
13) Clicamos en el botón «Next». Aparecerá otra ventana donde también de-
bemos hacer clic en el botón «Next».
15) Eclipse instalará los paquetes. Cuando termine es posible que nos pida
reiniciar el IDE. Si este es el caso, lo hacemos.
16) A partir de ahora podremos crear proyectos que usen JavaFX. Al hacer «File
→ New → Other» nos aparecerá una carpeta llamada «JavaFX» con plantillas.
© FUOC • PID_00273929 178 Guía de Java
En cuanto a las vistas, cabe señalar que estas, o bien se crean programando en
Java, o bien se crean mediante unos ficheros llamados FXML, que no dejan
de ser unos ficheros que contienen un código XML ad hoc para JavaFX. La
primera opción solo debe usarse para crear componentes (o toda la vista si
fuera necesario) en tiempo de ejecución (por ejemplo, mostrar un botón tras
marcar un checkbox). La segunda forma, la más habitual, se asemeja a otros
tipos de desarrollo, como es la creación de apps en Android (donde la parte
visual de la vista es un fichero XML y la parte de interacción de la vista es una
clase codificada en Java) o el front-end de una web (la parte visual hecha con
HTML+CSS y la parte interactiva que se comunica con el controlador hecha
en JavaScript).
Para que se vea más claro, pensad que cada pantalla/vista de vuestro programa
será un fichero FXML (parte visual) más un fichero Java (parte interactiva y
de comunicación con el controlador). Gracias a esto se logra separar/desaco-
plar lo visual (es decir, forma, estilo y ubicación de un botón) de la lógica (es
decir, funcionamiento/comportamiento del programa al hacer clic en un bo-
tón). Asimismo, como cada vista tiene un fichero Java para la parte interactiva,
desde él podremos añadir, mediante código, componentes a la vista si fuera
necesario, combinando así los dos modos de creación de componentes (esto
es, vía código y vía FXML).
(25)
Seguro que estáis pensando en que debe de ser muy laborioso hacer una inter- http://gluonhq.com/pro-
ducts/scene-builder/
faz si la tenemos que hacer escribiendo código FXML (que es un XML ad hoc).
Tenéis toda la razón, es muy arduo crear interfaces visuales mediante código
FXML y además «a ciegas». Por eso existen programas que ayudan a realizar
los ficheros FXML. Uno de estos programas es SceneBuilder.25 SceneBuilder
permite crear cada pantalla/vista de nuestro programa de manera visual, es
decir, arrastrando y soltando componentes en la escena. Una vez tengamos
la pantalla/vista con la apariencia que deseemos, solo tenemos que pedirle a
SceneBuilder que genere el fichero FXML que codifica la pantalla/vista que
hemos creado. ¡Así de simple!
© FUOC • PID_00273929 179 Guía de Java
Figura 39
proyecto Eclipse. A veces hay que refrescar el proyecto (por ejemplo, haciendo
F5 en Eclipse). Si en vez de abrir un fichero FXML con SceneBuilder, lo abrimos
haciendo doble clic, entonces veremos el código FXML en el editor de Eclipse.
(26)
Las vistas, es decir, cada FXML, puede ser personalizada mediante un fichero Acrónimo de cascade style
sheet.
CSS (o incluso varios FXML pueden compartir un mismo fichero CSS). Un fi-
chero CSS26 es un documento en el que se especifican diferentes estilos, desde
generales hasta concretos (por ejemplo, para un botón determinado). Estos
ficheros son esenciales cuando se programan webs, ya que facilitan la escala-
bilidad, el mantenimiento y la gestión de posibles cambios. JavaFX usa CSS
aunque utiliza sus propias propiedades, a pesar de que muchas son similares a
las propiedades web, por lo que es sencillo usarlas si se sabe CSS web. Nosotros
esto no lo trataremos aquí.
Figura 40
Hasta ahora solo hemos visto cómo crear la parte visual de una interfaz/vis-
ta/pantalla, pero ¿cómo le damos vida? ¿Cómo interactúa el usuario con ella?
Pues bien, esto se hace mediante eventos (tanto en JavaFX como en Swing y
AWT). Esto implica una nueva forma de programar que es denominada pro-
gramación�orientada�a�eventos�(POE). En la POE, el flujo de ejecución del
programa viene definido por las acciones/eventos (por ejemplo, clic, doble
clic, presionar una tecla, etc.) realizados por un agente externo al programa,
© FUOC • PID_00273929 181 Guía de Java
Figura 41
Imaginad que creamos un método llamado handleReturn que sirve para volver a la vis-
ta/pantalla anterior a la que estamos. En este momento debemos vincular un elemento
de la interfaz con el método handleReturn. Para hacer esta asignación/vinculación, lo
más cómodo es usar SceneBuilder. Abrimos el fichero FXML con SceneBuilder, seleccio-
namos un Button que hayáis puesto en la interfaz y en el apartado «Code» del lateral
derecho buscamos la opción «On Mouse Released» (véase figura 42). Allí hemos escri-
to/seleccionado el nombre de método a ejecutar, en este caso, handleReturn. Esta asig-
nación también la podéis ver reflejada, obviamente, en el código FXML.
Figura 42
campo llamado «fx:id». Lo habitual es ponerle como nombre uno que también
usemos como atributo en el controlador (precedido de @FXML). De esta forma,
el controlador puede acceder a este componente para modificarlo o consul-
tarlo, y la vista a la información del atributo de la clase de tipo controladora.
O dicho de otro modo, el atributo de la clase controladora hace referencia al
componente de la vista porque tienen el mismo nombre y porque en la clase
controladora hemos puesto @FXML delante del atributo.
Figura 43
5.3. JUnit 5
La librería JUnit permite a los desarrolladores hacer test unitarios. En este apar-
tado veremos qué es un test unitario y cómo usar JUnit con Eclipse.
Los test�unitarios (unit tests) son las pruebas de más bajo nivel en la
programación. Con ellas se comprueba el funcionamiento de las uni-
dades lógicas independientes, normalmente los métodos. Son pruebas
muy rápidas de llevar a cabo y son las que los desarrolladores están
constantemente pasando para verificar que su software funciona ade-
cuadamente.
Estos son los test más importantes. Cada test unitario es un paso que andamos
en el camino de la implementación correcta del software. Los desarrolladores
utilizan los test unitarios para asegurarse de que el código funciona como es-
© FUOC • PID_00273929 184 Guía de Java
peran que funcione, al igual que el cliente usa los test de aceptación/cliente
para asegurarse de que los requisitos de negocio se cumplan con el software
como se espera que lo haga. Todo test unitario debe ser:
• Rápido: puesto que ejecutamos un gran número de test cada pocos mi-
nutos, resultaría muy poco productivo tener que esperar unos cuantos se-
gundos cada vez. Un único test tiene que ejecutarse en una pequeña frac-
ción de segundo.
Figura 44
Figura 45
(27)
3) En la siguiente pantalla escogemos la versión más alta de JUnit27 y decimos En el momento de la creación
de esta guía, versión 5.
«Finish» (véase figura 46). A continuación vemos la pantalla de la figura 44 con
JUnit añadida. Pulsamos en «Apply and Close». Ahora en el «Package Explorer»
vemos que se ha añadido la librería JUnit 5. A partir de aquí ya podemos usar
JUnit para hacer test unitarios.
© FUOC • PID_00273929 186 Guía de Java
Figura 46
Figura 47
2) Ahora añadimos una nueva clase llamada Person. Una vez creado el fichero
Person.java, escribimos los atributos, como se muestra en la figura 48.
© FUOC • PID_00273929 187 Guía de Java
Figura 48
Figura 49
Figura 50
Figura 51
Figura 52
© FUOC • PID_00273929 189 Guía de Java
Figura 53
8) De este modo, nos solicita los métodos del código para los cuales se quiere
generar la plantilla de pruebas (véase figura 54). En esta ventana hacemos clic
en «Next» (la opción «New JUnit Jupiter Test» es lo mismo que decir «JUnit 5»).
Figura 54
© FUOC • PID_00273929 190 Guía de Java
Figura 55
10) Ahora en el «Package Explorer» nos ha aparecido una nueva clase llamada
PersonTest.java (véase figura 56) con el aviso de error. Una forma de quitar
los errores es eliminando el fichero module-info.java. Si miramos el códi-
go de PersonTest.java, veremos que son pruebas relacionadas 1:1 con los
métodos de la clase Person y que inicialmente serán fallidas para recordarnos
que deben ser implementadas. De hecho, sale el texto "Not yet implemen-
ted!". Podremos añadir más métodos de prueba anteponiendo la anotación
@Test delante de la firma de dicho método. Gracias a @Test ese método es
considerado prueba unitaria. Fijaos que si un método está sobrecargado, como
puede ser el constructor, para diferenciarlo le añade como coletilla los tipos de
los argumentos que recibe. Por ejemplo, testPersonIntStringString es el
método de testeo que crea JUnit para el constructor con argumentos, Person
(int age, String name, String surname).
© FUOC • PID_00273929 191 Guía de Java
Figura 56
Figura 57
© FUOC • PID_00273929 192 Guía de Java
12) Ahora nos aparece una pantalla en la que se muestra los test que NO se han
superado y el número de test que han fallado. En este caso hemos configurado
ocho test para ocho métodos de los que queríamos comprobar su funcionali-
dad y todas son fallidas (véase figura 58). Es posible que en Windows 10 os
salte una pantalla de aviso del Firewall que deberéis aceptar.
Figura 58
Figura 59
14) Mirando el código de la figura 59, se puede ver que el primer paso consiste
en cambiar la firma del método para que lance/propague las excepciones que
recibe su código, en este caso, el método setAge lanza Exception. Ya en el
cuerpo del método, lo primero es crear un objeto que disponga del método que
se quiere comprobar. Por consiguiente, se crea un objeto de la clase Person.
© FUOC • PID_00273929 193 Guía de Java
Nuestra persona por consiguiente debería tener una edad de diez años. Para
comprobar que es así, el framework JUnit aporta métodos especiales denomi-
nados asserts que nos deben devolver valores booleanos (true o false) para
poder comprobar que los métodos funcionan adecuadamente. Por ejemplo, se
hace uso del método assertEquals, el cual está comprobando que el valor
10 es igual que el valor obtenido por instance.getAge().
15) Si volvemos a ejecutar «Run As → JUnit Test», veremos que ahora solo
tenemos siete test fallidos, en vez de ocho. En el listado, testSetAge aparece
con un visto/check verde, lo que significa que ha pasado la prueba correcta-
mente.
© FUOC • PID_00273929 194 Guía de Java
Figura 60
16) Si hacemos clic en alguno de los métodos fallidos en la ventana JUnit que
se muestra en la figura 58, en la parte inferior llamada «Failure Trace», se nos
indica dónde ha fallado dicho método/test. Cuando todos los métodos de test
de la clase PersonTest superen las pruebas, la franja roja se volverá verde y
todos los métodos tendrán el visto/check verde al lado. Además pondrá Runs:
8/8, Errors: 0, Failures: 0. Gracias al uso de los test sabemos que
las clases se comportan como se espera que lo hagan. Si vamos codificando
y pasamos los test y uno que no fallaba ahora falla, podremos detectarlo a
tiempo.
© FUOC • PID_00273929 195 Guía de Java
Bibliografía
Java Tutorials Learning Paths [en línea] [consulta: 3 de septiembre de 2020]. Disponible en:
https://docs.oracle.com/javase/tutorial/tutorialLearningPaths.html.
Niemeyer, Patrick; Knudsen, Jonathan (2002). Learning Java (Java Series) (2.ª ed.). Esta-
dos Unidos: O’Reilly Media, Inc.
Niemeyer, Patrick; Knudsen, Jonathan (2013). Learning Java (Java Series) (4.ª ed.). Esta-
dos Unidos: O’Reilly Media, Inc.
Sierra, Kathy; Bates, Bert (2009). Head First Java. A Brain-Friendly Guide (2.ª ed.). Estados
Unidos: O’Reilly Media, Inc.
Van der Linden, Peter (2004). Just Java 2 (6.ª ed.). Estados Unidos: Prentice Hall, PTG.