Está en la página 1de 69

9/7/23, 20:20 ies-al-andalus.github.

io/Programacion/

Programación
Repositorio sobre programación en java del IES Al-Ándalus
Programación
Contenidos
Autor
Licencia
Contribuciones

Programación
En este repositorio iré colgando teoría y diferentes ejercicios, con sus respectivas posibles soluciones,
utilizados en el módulo de Programación de los ciclos Desarrollo impartidos en el IES Al-Ándalus.

Este repositorio no pretende ser una obra exhaustiva sobre programación, ni sobre java. Más bien pretende
ser una guía con las construcciones básicas y sobre todo un repositorio de ejercicios resueltos. La idea es
servir a cualquiera que se esté iniciando en programación y java para ejercitar sus avances por medio de los
ejercicios resueltos, que poco a poco iré ampliando, y ser una referencia rápida para su repaso.

Contenidos
Resolución de problemas mediante algoritmos
Introducción a la programación en java
Introducción a la programación orientada a objetos en java
Algunas clases interesantes en java
Estructuras dinámicas en java

Autor
José Ramón Jiménez Reyes

@JRJimenezReyes

joseramon.jimenez@iesalandalus.org

Profesor e informático. Gran defensor del sotfware libre, la colaboración y el aprendizaje para toda la vida.

Mi afición por el mundo de los ordenadores surgió siendo un niño cuando mis padres me regalaron un ZX
Spectrum (aún conservo las revistas Micro Hobby con las que empecé mi andadura por la informática y la
autoformación). Con los años me licencié en Informática en la Universidad de Granada, comencé a trabajar
como programador y después como administrador de redes y responsable de seguridad.

Hoy día ejerzo como profesor de Informática en el IES Al-Ándalus de Almería.

https://ies-al-andalus.github.io/Programacion/ 1/2
9/7/23, 20:20 ies-al-andalus.github.io/Programacion/

Licencia
Este proyecto tiene licencia del MIT - veáse el fichero de licencia para más detalles.

Contribuciones
Os animo a que realicéis vuestras contribuciones en el proyecto en la medida de las posibilidades de cada
cuál:

Sugiriendo mejoras.
Aportando nuevos ejemplos.
Corrigiendo erratas.
Etc.

Programación mantenido por IES-Al-Andalus

Ver en GitHub

https://ies-al-andalus.github.io/Programacion/ 2/2
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Programación
Repositorio sobre programación en java del IES Al-Ándalus
Resolución de problemas mediante algoritmos
Algoritmo
Estructura de un algoritmo en pseudocódigo
Variables
Tipos de datos
Operadores
Funciones
Sentencias secuenciales
Sentencias condicionales
Sentencias repetitivas
Personalización del lenguaje
Ejercicios

Resolución de problemas mediante algoritmos


En este apartado encontrarás teoría y ejercicios, con sus respectivas posibles soluciones, sobre la resolución
de problemas mediante algoritmos.

A la hora de escribir los algoritmos utilizaré la sintaxis propuesta en el programa PSeInt. Esta
documentación, además, está basada en la ayuda de dicho programa, aunque la idea principal es ir añadiendo
ejercicios resueltos que te puedan ayudar en tu aprendizaje.

José Ramón Jiménez Reyes

@JRJimenezReyes

joseramon.jimenez@iesalandalus.org

Contenidos

Algoritmo
Estructura de un algoritmo en pseudocódigo
Variables
Tipos de datos
Operadores
Funciones
Sentencias secuenciales
Sentencias condicionales
Sentencias repetitivas
Ejercicios

https://ies-al-andalus.github.io/Programacion/algoritmos/ 1/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Algoritmo
Un algoritmo no es más que un conjunto finito de instrucciones, expresado en un lenguaje bien definido que
nos permite llegar a la solución de un problema dado.

Para expresar un algoritmo podemos utilizar diferentes lenguajes o gráficos. Nosotros nos centraremos en la
sintaxis del pseudocódigo utilizado por el programa didáctico PSeInt que además también permite ver el
diagrama de flujo para un algoritmo escrito utilizando su sintaxis.

Dicho programa permite expresar un algoritmo en pseudocódigo utilizando diferentes variantes dependiendo
de la sintaxis escogida. Nosotros personalizaremos dicha sintaxis para intentar asemejar la sintaxis de
nuestros algoritmos a la utilizada por java, en la medida de lo posible.

Estructura de un algoritmo en pseudocódigo


Todo algoritmo en pseudocódigo tiene la siguiente estructura general:
Algoritmo SinTitulo
instruccion 1;
instruccion 1;
.
.
.
instruccion n;
FinAlgoritmo

El algoritmo comienza con la palabra clave Algoritmo seguida del nombre del mismo.
Le sigue una secuencia de instrucciones.
Finaliza con la palabra FinAlgoritmo.

Variables
Una variable en un algoritmo es un identificador en el que podemos almacenar información. El valor
almacenado en una variable puede ir variando a medida que el algoritmo avanza.

El nombre o identificador de la variable debe comenzar con letras, y puede contener solo letras, números y el
guión bajo.

Para acostumbrarnos, debe empezar en minúscula y si queremos utilizar varias palabras para
nombrarla, las pondremos juntas y la segunda y restantes comenzarán en mayúscula: numero,
numCifras, fechaNacimiento…
No debe coincidir con una palabra reservada o función del lenguaje, para no generar ambigüedad.
El nombre de una variable debe ser lo más explicativo posible y así nos ayudará a comprender su
cometido.
Toda variable tiene un tipo de dato asociado y sólo podrá contener datos de ese tipo (aunque en
algunos lenguajes de programación esta afirmación no es cierta).

Aunque en PSeInt no es obligatorio, debemos acostumbrarnos a definir la variable antes de utilizarla:

numero Es Entero

numero, numCifra Son Enteros

Tipos de datos
Nosotros utilizaremos tres tipos de datos básicos (aunque en PSeInt y en cualquier lenguaje de programación
existen más):
https://ies-al-andalus.github.io/Programacion/algoritmos/ 2/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Numérico: números, tanto enteros como reales. Para separar decimales se utiliza el punto. Ejemplos:
21 453 0 -3.6 3.14
Lógico: solo puede tomar dos valores: VERDADERO o FALSO.
Carácter: caracteres o cadenas de caracteres encerrados entre comillas (pueden ser dobles o simples,
aunque nos acostumbraremos a utilizar las dobles por su semejanza con java). Ejemplos "hola" "hola
mundo" "José Ramón"

Operadores
El lenguaje utilizado por PSeInt dispone de un conjunto básico de operadores que pueden ser utilizados para
la construcción de expresiones más o menos complejas los cuáles se muestran en las siguientes tablas:

Operadores relacionales

Operador Significado Ejemplo


> Mayor que 3>2
< Menor que ‘Hola’<’hola’
= Igual que 4=3
<= Menor o igual que 2<=2
>= Mayor o igual que 4>=5
<> Distinto que 7<>8

Operadores Lógicos

Operador Significado Ejemplo


&óY Conjunción (y) (8>5) & (5=3) //falso
|óO Disyunción (o) (8>5 | 5=3) //verdadero
~ ó NO Negación (no) ~(8>5) //falso

Operadores Algebraicos

Operador Significado Ejemplo


+ Suma suma <- op1 + op2
- Resta dif <- op1 - op2
* Multiplicación mult <- numero * 5
/ División porc <- 100 * parte / total
^ Potenciación sup <- 3.41 * radio ^ 2
% ó MOD Módulo (resto de la división entera) resto <- num MOD div

La precedencia de los operadores matemáticos es igual a la del álgebra, aunque puede alterarse mediante el
uso de paréntesis.

Funciones
Las funciones en pseudocódigo son parecidas a las que se utilizan en el álgebra, por ejemplo para hallar el
seno de un ángulo, aunque su sintaxis es algo diferente ya que los parámetros se encierran entre paréntesis.
Se coloca su nombre seguido de los argumentos para la misma encerrados entre paréntesis (por ejemplo
sen(x)). Se pueden utilizar dentro de cualquier expresión, y cuando se evalúe la misma, se reemplazará por
el resultado correspondiente. Actualmente, todas la funciones disponibles en PSeInt son matemáticas o de
cadena. A continuación se listan las funciones integradas en PSeInt disponibles:

https://ies-al-andalus.github.io/Programacion/algoritmos/ 3/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Función Significado
RAIZ(X) Raíz Cuadrada de X
ABS(X) Valor Absoluto de X
LN(X) Logaritmo Natural de X
EXP(X) Función Exponencial de X
SEN(X) Seno de X
COS(X) Coseno de X
TAN(X) Tangente de X
ASEN(X) Arcoseno de X
ACOS(X) Arcocoseno de X
ATAN(X) Arcotangente de X
TRUNC(X) Parte entera de X
REDON(X) Entero más cercano a X
AZAR(X) Entero aleatorio en el rango [0;x-1]
ALEATORIO(A,B) Entero aleatorio en el rango [A;B]
LONGITUD(S) Cantidad de caracteres de la cadena S
MAYUSCULAS(S) Devuelve una copia de la cadena S con todos sus caracteres en mayúsculas
MINUSCULAS(S) Devuelve una copia de la cadena S con todos sus caracteres en minúsculas
Devuelve una nueva cadena que consiste en la parte de la cadena S que va desde la
posición X hasta la posición Y (incluyendo ambos extremos). Las posiciones utilizan
SUBCADENA(S,X,Y)
la misma base que los arreglos, por lo que la primer letra será la 0 o la 1 de acuerdo al
perfil del lenguaje utilizado.
CONCATENAR(S1,S2) Devuelve una nueva cadena resultado de unir las cadenas S1 y S2.
Recibe una cadena de caracteres que contiene un número y devuelve una variable
CONVERTIRANUMERO(X)
numérica con el mismo.
Recibe un real y devuelve una variable numérica con la representación como cadena
CONVERTIRATEXTO(S)
de caracteres de dicho real.

Sentencias secuenciales
En PSeInt podemos encontrarnos con 3 tipos de sentencias secuenciales (que se ejecutan una detrás de otra):

Asignación: Como su nombre indica sirve para asignar valores a una variable. Utiliza el operador de
asignación, que en PSeInt es el símbolo <- (aunque también permite otros, nosotros nos quedaremos
con este).

Para realizar la asignación, primero se evalua la expresión de la derecha y luego se asigna el resultado.

Nos debemos acostrumbrar a utilizar variables que ya estén definidas.

num <- 10

suma <- operador1 + operador2

Lectura Esta sentencia permite leer valores por teclado y asignarlos a variables.

Leer num

Leer operador1, operador2

Escritura Sentencia que muestra información por pantalla al usuario.

Escribir "La suma es: ", suma

https://ies-al-andalus.github.io/Programacion/algoritmos/ 4/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Escribir Sin Saltar "Introduce un número: "

Sentencias condicionales
Son sentencias que permiten alterar el flujo del algoritmo y ejecutar unas sentencias u otras dependiendo del
valor de una condición.

Condicional Permite evaluar una condición y ejecutar una serie de sentencias si dicha condición es
verdadera u otras si es falsa.

Si <condicion> Entonces
<instruccionesV>
SiNo
<instruccionesF>
FinSi

Evalúa la condición y si es verdadera ejecutará <sentenciasV> y si es falsa ejecuturá <sentenciasF>

El bloque SiNo no es obligatorio y en ese caso (la condición es falsa) no se ejecuta ninguna sentencia y
se continúa por la sentencia después del FinSi

Selección múltiple En este caso nos permite seleccionar las instrucciones a ejecutar dependiendo del
valor de una variable numérica.
Segun <variable> Hacer
<número1>:
<instrucciones1>
<número2>,<número3>:
<instrucciones23>
<...>
De Otro Modo:
<instrucciones>
FinSegun

Al ejecutarse, se evalúa el contenido de la variable y se ejecuta la secuencia de instrucciones asociada


con dicho valor.

Si una opción incluye varios números, la secuencia de instrucciones asociada se debe ejecutar cuando
el valor de la variable es uno de esos números.

Opcionalmente, se puede agregar una opción final, denominada De Otro Modo, cuya secuencia de
instrucciones asociada se ejecutará sólo si el valor almacenado en la variable no coincide con ninguna
de las opciones anteriores.

Sentencias repetitivas
Son sentencias que también alteran el flujo del algoritmo, permitiendo repetir una secuencia de instrucciones
mientras se de alguna condición. También son conocidas como bucles.

Mientras Esta instrucción ejecuta una secuencia de instrucciones mientras se cumpla una condición.
Mientras <condicion> Hacer
<instrucciones>
FinMientras

Se evalúa la condición y si es verdadera se ejecuta la secuencia de sentencias. En cada paso se vuelve a


repetir el proceso.

La secuencia de instrucciones no tiene por qué ejecutarse ni una sola vez, si al principio la condición
es falsa.

https://ies-al-andalus.github.io/Programacion/algoritmos/ 5/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Si la condición siempre es verdadera entraremos en un bucle infinito debido a que la secuencia de


instrucciones no hace que la condición llegue a ser falsa.

Repetir Esta sentencia ejecuta un conjunto de instrucciones también mientras una condición sea
verdadera, pero esta condición se evalúa al final del bucle. PSeInt tiene otras sentencias Repetir pero
nosotros utilizaremos la siguiente por su similitud con java.

Repetir
<instrucciones>
Mientras Que <condicion>

La secuencia de instrucciones siempre se ejecuta al menos una vez, al contrario que en el bucle
anterior.

También debemos modificar las variables que afectan a la condición en la secuencia de instrucciones
del cuerpo del bucle o de lo contrario se puede entrar en un bucle infinito.

Para Este tipo de bucles se utiliza para repetir una secuencia de instrucciones un número determinado
de veces.

Para <variable> <- <inicial> Hasta <final> Con Paso <paso> Hacer
<instrucciones>
FinPara

La variable toma el valor <inicial> y se ejecuta la secuencia de intrucciones. Se incrementa la variable


el valor de <paso> y se comprueba si la variable ha superado el valor <final>. Si no lo ha superado, se
vuelve a ejecutar la secuencia de sentencias y se repite el proceso hasta que la variable supera el valor
<final>.

La claúsula Con Paso se puede obviar, en cuyo caso el incremento será de 1.

Personalización del lenguaje


Para que todos y todas utilicemos la misma sintaxis del lenguaje utilizado por PSeInt y que éste se asemeje lo
máximo posible a como luego se comportará Java, hemos utilizado la siguiente personalización del lenguaje,
por lo que os ruego que todos configuremos el programa PSeInt con dicha personalización.

https://ies-al-andalus.github.io/Programacion/algoritmos/ 6/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Ejercicios
Sentencias secuenciales

Asignar valor a una variable y mostrarlo

https://ies-al-andalus.github.io/Programacion/algoritmos/ 7/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Diseñar un algoritmo que le asigne un valor cualquiera a una variable entera y lo muestre por pantalla.

Lectura/Escritura de un número

Diseñar un algoritmo que lea un número por teclado y nos lo muestre por pantalla.

Lectura/Escritura de una cadena

Diseñar un algoritmo que lea tu nombre por teclado y te salude por pantalla.

Mostrar el doble de un número

Diseñar un algoritmo que lea un número entero por teclado y muestre el doble del mismo por pantalla.

Mostrar el cuadrado de un número

Diseñar un algoritmo que lea un número entero por teclado y muestre el cuadrado del mismo por
pantalla.

Hallar el perímetro de un rectángulo

Diseñar un algoritmo que lea la base y la altura de un rectángulo por teclado y muestre el perímetro del
mismo por pantalla.

Calcular el área de un círculo

Diseñar un algoritmo que lea el radio de un círculo por teclado y muestre el área del mismo por
pantalla.

Sentencias condicionales

Valor absoluto de un número

Diseñar un algoritmo que lea un número por teclado y muestre el valor absoluto del mismo por
pantalla.

Número par o impar

Diseñar un algoritmo que lea un número por teclado y nos indique si es par o impar.

Número positivo o negativo

Diseñar un algoritmo que lea un número por teclado y nos indique si es positivo o negativo.

Número entre 0 y 100

Diseñar un algoritmo que lea un número por teclado y nos indique si éste se encuentra entre 0 y 100
ambos inclusive.

Calificación obtenida

Diseñar un algoritmo que lea una nota por teclado y muestre si estás aprobado o suspenso por pantalla.

Números iguales

Diseñar un algoritmo que lea dos números por teclado y muestre si son iguales o no por pantalla.

Ordenar dos números

https://ies-al-andalus.github.io/Programacion/algoritmos/ 8/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Diseñar un algoritmo que lea dos números por teclado y los muestre ordenados de menor a mayor por
pantalla.

Ordenar tres números

Diseñar un algoritmo que lea tres números por teclado y los muestre ordenados de menor a mayor por
pantalla.

Sentencias repetitivas

Tabla de multiplicar

Diseñar un algoritmo que lea un número por teclado y muestre por pantalla la tabla de multiplicar de
dicho número.

Número positivo

Diseñar un algoritmo que lea un número positivo por teclado y lo muestre por pantalla.

Sumar números

Diseñar un algoritmo que lea números por teclado hasta que se introduzca un cero y muestre por
pantalla la suma de los mismos.

Media números

Diseñar un algoritmo que lea números por teclado hasta que se introduzca un cero y muestre por
pantalla la media de los mismos.

Ejercicios variados

Contar vocales de una frase

Diseñar un algoritmo que lea una frase por teclado y muestre por pantalla el número de vocales de
dicha frase.

Número perfecto

Diseñar un algoritmo que lea un número entero mayor que cero por teclado y nos informe si dicho
número es perfecto o no. Un número es perfecto si es igual a la suma de sus divisores.

Cantidad de cifras de un número

Diseñar un algoritmo que lea un número entero mayor que cero por teclado y nos informe de la
cantidad de cifras que posee dicho número. Para ello sólo debes utilizar operaciones aritméticas.

Descomponer cifras de un número

Diseñar un algoritmo que lea un número entero mayor que cero por teclado y nos muestre en cada
línea las cifras del mismo en orden de izquierda a derecha. Para ello sólo debes utilizar operaciones
aritméticas.

Descomponer cifras en orden inverso de un número

Diseñar un algoritmo que lea un número entero mayor que cero por teclado y nos muestre en cada
línea las cifras del mismo en orden de derecha a izquierda. Para ello sólo debes utilizar operaciones
aritméticas.

Mostrar el reverso de un número


https://ies-al-andalus.github.io/Programacion/algoritmos/ 9/10
9/7/23, 20:21 ies-al-andalus.github.io/Programacion/algoritmos/

Diseñar un algoritmo que lea un número entero mayor que cero por teclado y nos muestre el reverso
del mismo. Para ello sólo debes utilizar operaciones aritméticas.

Mostrar el reverso de un número sin modificarlo

Diseñar un algoritmo que lea un número entero mayor que cero por teclado y nos muestre el reverso
del mismo, pero sin modificar el número introducido. Para ello sólo debes utilizar operaciones
aritméticas.

Comprobar si un número es capicúa

Diseñar un algoritmo que lea un número entero mayor que cero por teclado y nos indique si dicho
número es capicúa o no (un múmero es capicúa si su reverso y él son iguales). Para ello sólo debes
utilizar operaciones aritméticas.

Programación mantenido por IES-Al-Andalus

Ver en GitHub

https://ies-al-andalus.github.io/Programacion/algoritmos/ 10/10
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/

Programación
Repositorio sobre programación en java del IES Al-Ándalus
Introducción a la programación en Java
¿Qué es Java?
Primer programa en Java
Identificadores
Tipos de datos
Literales
Variables
Operadores
Comentarios
Sentencias
Ejercicios

Introducción a la programación en Java


En este apartado encontrarás una introducción a la programación en Java. He pretendido hacer una introducción teórica que os ayude, a modo de resumen, a
comprender los principales conceptos utilizados en Java. En este apartado no hablaremos de Programación Orientada a Objetos (todo esto en la medida de lo posible,
pues Java es un lenguaje orientado a objetos y todo se basa en dichos conceptos), ya que dichos conceptos los veremos en otros apartados. El objetivo de este
apartado es que creemos programas sencillos escritos en Java, utilizando sus características básicas.

Al igual que en otros apartados, el objetivo principal es que tengáis una amplía gama de ejercicios, con sus respectivas posibles soluciones, sobre la introducción a
la programación en Java.

José Ramón Jiménez Reyes

@JRJimenezReyes

joseramon.jimenez@iesalandalus.org

Contenidos

¿Qué es Java?
Primer programa en Java
Identificadores
Tipos de datos
Literales
Variables
Operadores
Comentarios
Sentencias
Ejercicios

¿Qué es Java?
Java es un lenguaje de programación creado a principios de los 80 por James Gosling, ingeniero de Sun Microsystems. Al principio lo llamó OAK, aunque luego le
cambiaron el nombre a Java (cuenta la leyenda que le dieron este nombre ya que Java es un tipo de café asiático y precisamente el café que bebían en el desarrollo
del mismo). Actualmente ha sido adquirido por la empresa Oracle y es un lenguaje que no para de evolucionar.

Las características que tuvieron en mente a la hora de crear Java fueron las siguientes:

Orientado a Objetos: de todo esto hablaremos en mucha más profundidad en los siguientes apartados.
Simple: aunque se basaron en lenguajes como C y C++, intentaron reducir la complejidad de los mismos en su diseño.
Distribuido: proporcionando librerías para su fácil uso en aplicaciones en red.
Multihilo: permitiendo la utilización de hebras, que son procesos mucho más ligeros que la creación de nuevos procesos, para la ejecución de varias tareas a la
vez.
Seguro: ya que es la JVM la encargada de gestionar toda la memoria y no el programador.
Multiplataforma: fue uno de los principales objetivos en su diseño, ya que se crea un código intermedio que luego puede ser ejecutado en cualquier
plataforma que tenga instalada una JVM. Este es el dicho “write once - run everywhere” (escríbelo una vez y ejecútalo donde sea).

Java no sólo es un lenguaje de programación, sino que también lo podríamos considerar como un conjunto de tecnologías, entre las que podemos destacar (podrás
haber visto estas tecnologías escritas con un “2” entre medías, pero eso cambió como veremos posteriormente):

JSE: Java Standard Edition, que es la tecnología en la que nos basaremos y que se dedica principalmente al desarrollo de aplicaciones de escritorio.
JEE: Java Enterprise Edition, que es la utilizada para la creación de aplicaciones empresariales, servicos web, etc.
JME: Java Micro Edition, que es la utilizada para el desarrollo de aplicaciones para dispositivos con menor capacidad de procesamiento y/o almacenamiento.

Desde que surgió Java hasta el día de hoy podemos encontrar diferentes versiones del lenguaje:

JDK 1.0: Nacimiento de Java desde el proyecto OAK.


JDK 1.1: Se añaden nuevas librerías como AWT, JDBC, etc.
J2SE 1.2: Se incluye Swing.

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 1/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
J2SE 1.3: Se mejora el rendimiento y se añade JNDI.
J2SE 1.4: Se mejoran las expresiones regulares y se añaden assert.
J2SE 5: Sufre cambios mayores por lo que se cambia la numeración. Se añade: autoboxing, generics, varargs, etc.
JSE 6: Se elimina el 2 del nombre. Se da soporte para lenguajes de scripting, se vuelve a mejorar el rendimiento y se añade soporte para JDBC 4.0.
JSE 7: Se añade soporte para múltiples lenguajes. Se añaden cambios en el lenguaje como el uso de String en la sentencia swicth, try with resouces, etc.
JSE 8: Se añade el soporte de las funciones lambda (entre otras) para soportar la programación funcional.

Java es un lenguaje interpretado, o también llamado precompilado. Partiendo de un archivo fuente .java, lo compilamos para obtener un fichero .class, que es un
archivo que aún no es ejecutable por el SO pero que sí entiende la Máquina Virtual de Java (JVM). Ese fichero .class ahora ya podemos ejecutarlo en cualquier SO
o plataforma que tenga instalada una JVM, que será la que lea el fichero .class y lo vaya interpretando para ser ejecutado.

Para el desarrollo de aplicaciones en Java y su posterior ejecución disponemos de dos componentes principales:

JDK: Es el kit de desarrollo y en el que podemos encontrar, entre otros, el compilador de java que se encarga de traducir el código fuente a un código
intermedio que posteriormente podrá ser ejecutado por la JVM (máquina virtual de Java) que tengamos instalada en nuestro sistema.
JRE: Es el entorno de ejecución de Java específico de cada plataforma y en el que simplemente vienen las librerías y la JVM para nuestra plataforma y que
permite ejecutar las aplicaciones anteriormente compiladas con el kit de desarrollo (aunque el kit de desarrollo también contiene dicha JVM).

Últimamente ha habido un pequeño lío con la licencia de la máquina virtual ya que en 2018 Oracle anunció que a partir de enero de 2019 (Java 11) cobraría una
licencia comercial para que pudiese ser utilizado. A partir de septiembre de 2021 Oracle anunció que Java se distribuirá bajo licencia Oracle No-Fee Terms and
Conditions (NFTC) que permitiría el uso gratuito. Nota de prensa con el anuncio

Aún así, hay una implementación libre con versión GNU GPLv2 de Java llamada OpenJDK, en la que diferentes empresas y fundaciones contibuyen. Esta
implementación libre será la que nosotros utilizaremos. Podemos encontrar los binarios compilados para los diferentes SOs en la página del proyecto Eclipse
Temurim y trabajaremos con la última versión LTS, que a día de hoy es la versión 17.

Primer programa en Java


Para realizar nuestro primer programa en java, necesitaremos tener instalado, correctamente configurado el JDK en nuestro SO y, por ahora, disponer de un editor de
texto.

Cuando empezamos a familiarizarnos con cualquier lenguaje de programación, nuestro primer programa es el famoso Hola Mundo. En nuestro caso no iba a ser
menos. Por tanto, abriremos un editor de texto y copiaremos el siguiente código y lo guardaremos en una carpeta con el nombre HolaMundo.java.
public class HolaMundo {

public static void main(String[] args) {


System.out.println("Hola mundo!!!");
}

Una vez guardado con el nombre HolaMundo.java, simplemente deberemos compilarlo mediante el comando javac, pasando como argumento el nombre del archivo,
que nos generará el archivo HolaMundo.class. Este archivo corresponde con nuestro programa compilado pasado a bytecodes y será el archivo que podremos pasar a
cualquier sistema que tenga instalada la JVM para poder ejecutarlo.

Para ejecutarlo, simplemente ejecutaremos el comando java pasando como argumento el nombre del archivo (sin la extensión .class).

En la siguiente imagen se muestra cómo comprobar la versión que tenemos instalada, tanto de javac como de java (que generalmente serán la misma). Luego se
muestra el contenido del fichero. Se compila dicho fichero y se muestra cómo se genera el archivo .class. Y finalmente cómo se ejecuta el fichero generado y la
salida que produce el mismo.

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 2/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
Puede ser que te haya resultado algo engorroso y que seguramente estés pensando que si siempre hay que hacerlo así, que cuando sea un programa más complejo eso
es imposible, etc. Pero la buena noticia es que esto no es así ya que afortunadamente hoy existen los llamados Entornos de Desarrollo Integrados (IDE) que nos
facilitan la tarea. Gracias a estos IDEs la escritura de un programa es muy sencilla ya que te van haciendo sugerencias a la hora de escribir, tiene atajos de teclado
para sentencias muy comunes, la compilación es automática cada vez que se guarda, nos permite depurar nuestros programas mediante la ejecución paso a paso,
permiten la refactorización del código, etc, etc.

Los entornos más populares para el desarrollo de aplicaciones java son (aunque exsiten otros muchos):

Netbeans desarrollado por Oracle, aunque actualmente pertenece a la Fundación Apache. Descargar Netbeans.
Eclipse desarrollado por la fundación Eclipse. Descargar Eclipse.

Ambos entornos de desarrollo son multiplaforma por lo que podemos ejecutarlos en nuestro SO preferido y son muy parecidos. Las prestaciones de ambos son muy
similares y ambos poseen una gran cantidad de añadidos que podemos utilizar. Yo, personalmente, prefiero Eclipse, pero para gustos colores.

Ambos IDEs permiten crear un proyecto Java nuevo, para luego añadir una nueva Clase en la que podemos marcar que añada el método main para que dicha clase
podamos ejecutarla. Al hacerlos así, se nos creará un esqueleto con todo lo necesario para que podamos ir añadiendo sentencias entre las llaves que se abren y se
cierran después de la definición del método main. Sé que esto es hacer un gran acto de fe, pero con estas simples acciones podremos ir creando nuestros pequeños
programas Java que nos permitirán ir familiarizándonos con este lenguaje y para lo que te daré las pautas adecuadas en los siguientes apartados.

Por ahora, lo único importante que debes saber es que el nombre de la Clase (el nombre que va justo después de la palabra reservada class) debe coincidir con el
nombre del archivo .java. Aunque cuando el IDE te pregunta por el nombre de la clase, eso ya lo hace por tí.

A continuación te muestro un vídeo de cómo crear el programa HolaMundo en Eclipse.

0:00 / 1:10

Como podéis apreciar, el proceso es muy sencillo. Ahora sólo nos queda empezar a programar!!!

Identificadores
Los identificadores no son más que los nombres que les damos a algunos elementos de nuestro programa (variables, clases, métodos, etc) para poder referirnos a
ellos fácilmente.

Para nombrar un identificador debemos seguir las siguientes reglas:

No pueden ser una palabra reservada del lenguaje (que ahora veremos cuáles son).
El primer carácter de un identificador debe ser una letra, un número o los símbolos _ o $.
Puede incluir números.
Se distingue entre mayúsculas y minúsculas.
Aunque no es obligatorio, no debemos utilizar tildes ni la ñ.

Veamos ahora la lista de palabras reservadas que no podemos utilizar como identificador para evitar ambigüedades:

abstract continue for new swicth


assert default goto package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while

Anteriormente he mencionado las reglas para nombrar un identificador correctamente y que son de obligado cumplimiento. Sin embargo, en el mundo de java se
sigue una convención para el nombrado de los diferentes elementos del lenguaje, que debemos seguir. Esta convención no es obligatoria pero sí es conveniente que
la utilicemos y así con sólo ver un identificador sabremos a qué elemento se está refiriendo. En la siguiente tabla os muestro dicha convención para cada uno de los
elementos (de los que hablaremos posteriormente).

Tipo de
Convención Ejemplo
identificador
Empieza por una lerta mayúscula y cóntinua por minúsculas. Si es la concatenación de varias palabras, cada una de
Clase Hola, HolaMundo
ellas empieza también por letra mayúscula.

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 3/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
Tipo de
Convención Ejemplo
identificador
Se nombra todo en minúsculas. Si es la concatenación de varias palabras, cada una de ellas empieza también por letra sueldo,
Variable o Método
mayúscula. numeroCaracteres
Constante Se nombra todo en mayúsculas. Si es la concatenación de varias palabras, éstas se separan por el símbolo _ PI, CANTIDAD_MAXIMA

Ni que decir tiene que los identificadores deben ser lo suficientemente significativos para que nos transmitan el cometido de los mismos.

Tipos de datos
En Java existen dos grandes categorías de tipos de datos:

primitivos: Son los tipos de datos básicos en java y serán los que veamos en este apartado. Este tipo de datos vienen definidos de forma implícita en java.
referenciados: Son los tipos de datos que refenrencian a un objeto y los arrays. A este tipo de datos nos referiremos en apartados posteriores.

Un tipo de dato primitivo indica la cantidad de memoria que deberá reservar el compilador para almacenar una variable de ese tipo. Java es un lenguaje fuertemente
tipado por lo que es necesario definir el tipo de dato de cada una de las variables.

Los tipos de datos primitivos se pueden agrupar en: caracter, numérico, decimal y lógico. En la siguiente tabla se muestran los diferentes tipos de datos primitivos, su
tamaño, rango y el valor por defecto de cada uno de ellos:

Tipo de dato Tamaño Rango Valor por defecto


char 2 bytes \u0000 a \uFFFF \u0000
byte 1 byte -128 a 127 0
short 2 bytes -32768 a 32767 0
int 4 bytes -2147483648 a 2147483647 0
long 8 bytes -9223372036854775808 a 9223372036854775807 0
float 4 bytes 0.0
double 8 bytes 0.0
boolean 1 byte true o false false

Aunque String no es un tipo de dato primitivo ya que es una referencia a un objeto de la clase String, simplemente lo nombro ya que a veces lo utilizaremos.

Literales
Los literales son valores constantes que podemos utilizar para asignar a una variable o como parte de una expresión. Cada tipo de dato tiene su forma para
expresarlo. En la siguiente tabla muestro cómo expresar cada uno de ellos.

Tipo de
Representación
dato
char Encerrados entre ''
byte, shor,
Se representan por los dígitos del número.
int
long Como los anteriores pero terminados en l o L
Se representan por los dígitos del número. Para separar la parte entera de la decimal se utiliza el .. También se puede representar mediante su notación
double
exponencial utilizando el símbolo e
float Como los anteriores pero terminados en f o F
boolean true o false
String Encerrados entre ""

Variables
Una variable en java es una posición de memoria en la que se almacenará un valor del tipo de dato de la misma. Se representa por su identificador y en el programa
nos refererimos a ella mediante dicho identificador. El valor que contenga la variable podrá cambiar a lo largo de la vida del programa.

En java todas las variables deben ser declaradas antes de ser utilizadas. Para declarar una variable indicaremos su tipo y el identificador de la misma. También es
posible declarar varias variables del mismo tipo en la misma sentencia, separando los identificadores de las mismas por coma. La declaración, al ser una sentencia
como ya veremos, debe acabar con un ;. Aquí muestro algunos ejemplos de declaración de variables.

int posicion;
double precision;
boolean esPrimo;
float x, y, distancia;

Hasta que no declaramos una variable ésta no puede ser utilizada en nuestro programa. Al ser java un lenguaje fuertemente tipado no permite hacerlo y nos dará un
error de compilación (o nos lo avisará nuestro IDE).

Justo después de declarar una variable, ésta toma el valor por defecto del tipo asociado, aunque no podremos utilizar la misma hasta que no le asignemos un valor.
También es posible asignar un valor a la hora de declarar una variable por medio del operador =. Aquí muestro un ejemplo de ello.
int numeroCartasRestantes = 40;
float irpf = 21f, retencion;

Cuando se declara una variable se reserva el espacio necesario para almacenar el tipo de dato de la misma y este espacio se libera cuando el programa sale del
ámbito de la misma. Por ahora nuestros programas estarán todos contenidos dentro del método main. Por tanto, el ámbito de una variable será el bloque al que
pertenece, es decir las llaves más cercanas entre las que está encerrada dicha variable. Esto quedará más claro cuando empecemos a hablar de sentencias, pero es
importante que lo recordéis.

Operadores

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 4/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
Un operador es un símbolo utilizado para componer una expresión, relacionando uno, dos o tres operandos (en el caso de java). Por tanto, una primera clasificación
de los operadores podría ser: unarios, binarios y ternarios. Los operandos podrán ser variables o literales. La expresión evaluará a un valor dependiendo del tipo de
dato de los operandos y del operador en cuestión.

Otra clasificación de los operadores en java es la siguiente (sólo mostraré los que utilizaremos por ahora, para no liar):

Operadores relacionales

Operador Significado
> Mayor que
< Menor que
== Igual que
<= Menor o igual que
>= Mayor o igual que
!= Distinto que

Operadores lógicos

Operador Significado
&& ó & Conjunción (y)
|| ó | Disyunción (o)
! Negación (no)

La diferencia entre el operador && y el operador & es que el primero evalúa en cortocircuito y el segundo no. Por lo que si el operando izquierdo evalúa a false ya no
continúa evaluando ya que la expresión evaluará a false, evalúe a lo que evalúe el operando derecho. El segundo evalúa ambos operandos.

La diferencia entre el operador || y el operador | es que el primero evalúa en cortocircuito y el segundo no. Por lo que si el operando izquierdo evalúa a true ya no
continúa evaluando ya que la expresión evaluará a true, evalúe a lo que evalúe el operando derecho. El segundo evalúa ambos operandos.

Operadores algebraicos

Operador Significado
+ Suma
- Resta
* Multiplicación
/ División
% Módulo (resto de la división entera)
++ Incrementar una unidad
-- Decrementar una unidad

La división realizará la división entera o real dependiendo del tipo de cada uno de los operandos. Para que realice la división entera ambos operandos deberán ser
numéricos (int o long). Si uno de ellos no lo es realizará la división real.

Los operadores ++ y -- pueden ser prefijos o postfijos. Esto quiere decir que realizará la operación de incremento o decremento antes de evaluar la expresión a la
que pertenecen o después. Por ejemplo:
...
int x = 3;
boolean resultado;
resultado = ++x * 2 == 6 //resultado será false, x valdrá 4 después de la ejecución
resultado = x++ * 2 == 8 //resultado será true, x valdará 5 después de la ejecución
...

En las expresiones aritméticas debemos tener en cuenta que el compilador siempre intentará hacer una conversión implícita de los tipos de datos de los operadores y
los intentará convertir al de mayor precisión:

Si hay un double, el otro lo convierte a double.


Si no, pero hay un float, el otro lo convierte a float.
Si no, pero hay un long, el otro lo convierte a long.
En caso contrario los convierte a int.

También podemos hacer conversiones explícitas mediante el uso del casting, que no es más que anteponer al operando o expresión el tipo de dato al que queremos
convertir encerrado entre paréntesis. Esta conversión explícita o casting puede conllevar pérdida de información si estamos intentado hacer la conversión de un tipo
de dato de mayor precisión a uno de menor. Incluso algunas conversiones ni siquiera se podrán realizar y lanzarán un error.
...
int resultado, op1 = 2;
float op2 = 5f;
resultado = op1 / op2; //Esta operación daría un error ya que no se puede llevar a cabo la conversión
resultado = op1 / (int)op2; //resultado sería 0 ya que haría la división entera
...

Operadores de asignación

Operador Significado
= Asignación
+= Sumar el valor y asignar
-= Restar el valor y asignar
*= Multiplicar por el valor y asignar
/= Dividir por el valor y asignar

Los operadores de asignación para que tengan sentido deben tener una variable como operando izquierdo y lo que hacen es cambiar el valor de dicha variable.

Operador de concatenación de cadenas +

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 5/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
Precedencia de evaluación

A la hora de evaluar una expresión debemos tener en cuenta:

Lo que esté encerrado entre paréntesis es lo primero que se evalúa.


Seguidamente evaluamos los operadores de mayor prioridad a menor prioridad.
A igual prioridad los operadores se evalúan de izquierda a derecha.
Los operadores de igualdad son los de menor prioridad.
La prioridad de los demás operadores, de mayor a menor es la que sigue:
++, --, !, + (unario), - (unario)
*, /, %
+, -
<, <=, >, >=
==, !=
&
^
|
&&
||

Comentarios
Los comentarios son anotaciones que se hacen en los programas para aclarar algún tipo de funcionalidad y que son ignorados por el compilador, pero que a veces
vienen bien para explicar algún detalle. Es imprescindible no abusar de los comentarios ya que hacen el código difícil de leer. Es más, a veces ponemos
comentarios para explicar qué hace un fragmento de código y eso nos está indicando que ese comentario sobra y que ese fragmento de código habría que sustituirlo
por un método con un nombre explicativo de dicha función y con ello nuestro código queda autodocumentado. El mismo caso es cuando utilizamos nombres
apropiados a nuestras variables; hacen que aumente la legibilidad del mismo y no es necesario acompañarla de un comentario para explicar dicho cometido.

En java existen tres tipos de comentarios:

De una sola línea: El comentario se antecede de los caractes //.


De varias líneas: El comentario se encierra entre los caracteres /* y */.
Javadoc: Son comentarios utilizados para generar documentación y el comentario se encierra entre los caracteres /** y */. Por ahora, no utilizaremos este tipo
de comentarios.

Sentencias
En java, como en todos los lenguajes de programación, podemos encontrar tres grandes grupos de sentencias (esta clasificación es según mi parecer y para adecuarla
a lo que hemos visto en el apartado sobre pseudocódigo, aunque otros programadores o programadoras hacen otras clasificaciones): secuenciales, condicionales y
repetitivas. También hablaré escuentamente de las sentencias que rompen el flujo de un programa.

Todas las sentencias deben acabar con el símbolo ;. Una sentencia no es más que una orden específica que da el programa para realizar una determinada acción.

Vamos a verlas una a una:

Sentencias secuenciales

Son las sentencias que se ejecutan una detrás de otra, secuencialmente. Por lo que el flujo del programa es lineal.

Asignación: Como su nombre indica sirve para asignar valores a una variable. Utiliza cualquier operador de asignación de los vistos hasta el momento.

Para realizar la asignación, primero se evalúa la expresión de la derecha y luego se asigna el resultado de dicha evaluación a la variable de la izquierda. La
parte izquierda de la asignación debe ser una variable del mismo tipo de dato del resultante de la evaluación de la parte derecha.

Declaración: Como ya hemos dicho, en java es necesario declarar cualquier variable que vayamos a utilizar. Ya hablamos de cómo se declaraban las variables.
Simplemente comentar que una variable se puede declarar en cualquier punto del programa, pero que antes de utilizarla es necesario declararla.

Expresión: Una expresión por si misma puede que no tenga sentido si no va ligada a una asignación. Pero hay casos en los que eso no es del todo cierto, ya
que por ejemplo la sentencia posicion++; tiene un efecto sobre la variable posicion. Sin embargo, la sentencia, por poner un ejemplo, posicion > 0; por sí
misma no tiene sentido, aunque es válida.

Bloque de sentencias: Un bloque de sentencias es una agrupación de sentencias que se comportan como una unidad. Los bloques de sentencias se definen
encerrando la sentencia o sentencias entre los caracteres { y }. Además los bloques de sentencias se pueden anidar uno dentro de otro, como se muestra en el
siguiente ejemplo:
...
{
int numero = 5;
...
{
int posicion = -1;
...
}
...
}
...

Como se puede apreciar hay un bloque dentro de otro. Si os acordáis, ya hablamos del ámbito de las variables. Pues en este ejemplo podemos apreciar que el
ámbito de la variable numero es el bloque externo y el ámbito de la variable posicion es el bloque interno. Por lo que la variable posicion se creará al
declararla en el bloque interno y se destruirá al salir de dicho bloque, por lo que fuera del mismo no nos podremos referir a ella. Sin embargo el ámbito de la
variable numero es el bloque externo, por lo que dentro del bloque interno también podremos referirnos a ella ya que todavía sigue existiendo.

En todo el programa es imprescindible respetar la indentación correcta, para que la legibilidad del código sea adecuada. Esto cobra vital importancia cuando
hablamos de bloques y bloques anidadas, ya que si no nos será muy difícil distinguir qué sentencia pertenece a qué bloque.

Sentencia nula: Aunque el caracter ; es el terminador de sentencias, también se puede considerar como la sentencia nula, al igual que se podría considerar un
bloque de sentencias vacío {}. Hay que tener cuidado con esto y, si se utiliza, hacerlo con cautela. En los ejercicios veremos algunos ejemplos del peligro que
puede conllevar un mal uso de la misma.

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 6/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
Llamadas a métodos: Una llamada a un método no es más que la instrucción que indica que se ejecute el código asociado a dicho método. No me quiero
parar en esto ahora, pero ya hemos realizado una llamada a un método sin saberlo en nuestro primer programa: System.out.println("Hola Mundo!!!"). Como
ya dije, esto es hacer un acto de fe, pero simplemente estamos ejecutando el método llamado println, perteneciente a la clase System, asociado a un miembro
de dicha clase llamado out. Todo esto quedará más claro cuando hablemos de los conceptos relativos a la Orientación a Objetos.

Dentro de esta categoría querría destacar los métodos encargados de la Entrada / Salida.

Salida: Para realizar una salida por consola podemos utilizar las siguientes sentencias (en otros apartados comprenderemos su significado):

System.out.println(cadena): Imprime en la consola la cadena cadena y un salto de línea.


System.out.print(cadena): Hace lo mismo pero no imprime el salto de línea.

Como ya os comenté, en este apartado a veces nos va a hacer falta trabajar con cadenas (String), aunque no sean un tipo primitivo. Ya comentamos que
los literales tipo cadena iban encerrados entre "" y que podíamos concatenar (unir) cadenas mediante el operador +. Además, siempre que utilicemos el
operador + con al menos un operando de tipo String, java convertirá el otro operando a otra cadena. Por lo que los siguientes ejemplos serían válidos:

...
int posicion = 0;
String miCadena = "Hola";
System.out.println(miCadena); //Imprime el valor de la variable miCadena que es "Hola"
System.out.println("Hola Mundo"); //Imprime el literal "Hola Mundo"
System.out.println("La posición ocupada es: " + posicion) //Imprime "La posición ocupada es: 0"
...

Lectura: Para realizar la lectura desde teclado en java hay que tener conocimientos más avanzados, que ahora no vamos a detallar ya que podrían hacer
perdernos en los detalles. Por ello he desarrollado una librería que os facilitará la vida y podréis llevarla a cabo por medio de una sola sentencia,
evitando tener en cuenta todos los detalles asociados.

He creado una librería que empaqueta la clase Entrada que es la que contine los diferentes métodos de lectura. Para poder utilizarla, simplemente tenéis
descargaros la última versión de la misma de mi repositorio. En la siguiente imagen se puede ver cómo poder descargar la última versión a día de hoy (el
archivo entrada-1.0.3.jar en este caso).

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 7/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/

En el siguiente vídeo podrás ver cómo integrar dicha librería en tu proyecto en Eclipse.

0:00 / 2:16

Si por el contrario utilizas Gradle para gestionar tus dependencias, simplemente desbes incluir en tu fichero build.gradlelo siguiente:
https://ies-al-andalus.github.io/Programacion/introduccionJava/ 8/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
repositories {
...
maven { url 'https://jitpack.io' }
}

dependencies {
...
api 'com.github.JRJimenezReyes:entrada:1.0.3'
}

La clase Entrada nos ofrece los siguientes métodos para leer algunos de los tipos primitivos vistos en este apartado y que podemos utilizar de la
siguiente forma (he mostrado la declaración y la asignación juntas para recalcar el tipo de dato al que podemos hacer la asignación, pero podría estar
dividida en dos sentencias separadas):

char caracter = Entrada.caracter();


String cadena = Entrada.cadena();
int entero = Entrada.entero();
long largo = Entrada.enteroLargo();
float real = Entrada.real();
double realDoble = Entrada.realDoble();

Sentencias condicionales

Son sentencias que permiten alterar el flujo del programa y ejecutar unas sentencias u otras dependiendo del valor de una condición.

Condicional: Permite evaluar una condición y ejecutar una serie de sentencias si dicha condición es verdadera u otras si es falsa.
if <condicion> {
<sentenciasV>
} else {
<sentenciasF>
}

Evalúa la condición y si es verdadera ejecutará <sentenciasV> y si es falsa ejecuturá <sentenciasF>

El bloque else no es obligatorio y en ese caso (la condición es falsa) no se ejecuta ninguna sentencia y se continúa por la sentencia después del bloque if.

Si la condición es verdadera y en ese caso sólo queremos ejecutar una sóla sentencia, podríamos prescindir del bloque y eliminar los caracteres {}. Aunque
esto no te lo aconsejo y es mejor que te acostumbres a poner las llaves. Esto también es aplicable a la parte else.

Dentro de cada uno de los bloques, se podría utilizar otra sentencia if y es a lo que llamamos if anidados. Hay que tener en cuenta que cada else va asociado
a su if más cercano que no tenga ya asociada una sentencia if.
...
if (numero > 0) {
if (numero % 2 == 0) {
System.out.println("El número es par");
}
if (numero % 3 == 0) {
System.out.println("El número es divisible por 3");
} else { //Este else va asociado al if (numero % 3 == 0)
System.out.println("El número no es divisible por 3");
}
}
...

Otra construcción común es utilizar sentencias del tipo if-else-if como se puede ver en este otro ejemplo.
...
if (numero % 2 == 0) {
System.out.println("El número es par");
} else if (numero % 3 == 0) {
System.out.println("El número no es par, pero es divisible por 3");
} else {
System.out.println("El número no es par ni divisible por 3");
}
...

Operador ternario: Cuando hemos hablado de los operadores, no he querido hablaros del operador ternario para no liaros. Pero, llegados a este punto, toca
hablar de él. Este operador ternario no es una sentencia de control de flujo como tal, pero evalúa a un determindo valor dependiendo de una condición. Se
suele utilizar en las asignaciones, aunque también se puede utilizar en otras ocasiones que no me detendré ya que aún no hemos visto.

La sintaxis es la siguiente: <condicion> ? <expresionV> : <expresionF>. Su funcionamiento es el siguiente:

Si la condición es verdadera, entonces evalúa a expresionV.


Si la condicion es falsa, entonces evalúa a expresionF
...
String mensaje;
int numero;
System.out.print("Introduce un número: ");
numero = Entrada.entero();
mensaje = (numero % 2 == 0) ? "El número es par" : "El número es impar";
System.out.println(mensaje);
...

Selección múltiple: En este caso se nos permite seleccionar las instrucciones a ejecutar dependiendo del valor de una expresión entera, caracter o cadena.

swicth (<expresion>) {
case <valor1>:
<sentencias1>
break;
case <valor2>, <valor3>:
<sentencias23>
break;
<...>
[ default:

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 9/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
<otrasSentencias> ]
}

Al ejecutarse, se evalúa la expresión y se ejecuta la secuencia de instrucciones asociada con el valor correspondiente.

Si una clausula case incluye varios valores, la secuencia de instrucciones asociada se debe ejecutar cuando la expresión evalúe a uno de esos valores.

Opcionalmente, se puede agregar una opción final, denominada default, cuya secuencia de instrucciones asociada se ejecutará sólo si el valor almacenado en
la variable no coincide con ninguna de las opciones anteriores. Esta clausula es opcional y se puede omitir.

La sentencia break hace que no se siga haciendo comprobaciones.

Para cada grupo de sentencias no es necesario utilizar los caracteres {} para delimitar los bloques de cada opción ya que en esta construcción cada bloque está
perfectamente delimitado.

Sentencias repetitivas

Son sentencias que también alteran el flujo de un programa, permitiendo repetir una secuencia de instrucciones mientras se cumpla alguna condición. También son
conocidas como bucles.

while: Esta sentencia ejecuta otras sentencias mientras se cumpla una condición.
while <condicion> {
<sentencias>
}

Se evalúa la condición y si es verdadera se ejecuta la secuencia de sentencias. En cada paso se vuelve a repetir el proceso.

La secuencia de sentencias no tiene por qué ejecutarse ni una sola vez, si al principio la condición es falsa.

Si la condición siempre es verdadera entraremos en un bucle infinito debido a que la secuencia de sentencias no hace que la condición llegue a ser falsa.

...
int numero;
System.out.print("Introduce un número entre 0 y 10 (ambos inclusive): ");
numero = Entrada.entero();
while (numero < 0 || numero > 10) {
System.out.println("ERROR: El número debe estar comprendido entre 0 y 10 (ambos inclusive)");
System.out.print("Por favor vuelve a introducir un número válido: ");
numero = Entrada.entero();
}
System.out.println("El número introducido es: " + numero);
...

do-while: Esta sentencia ejecuta un conjunto de sentencias también mientras una condición sea verdadera, pero esta condición se evalúa al final del bucle. Su
sintaxis es la siguiente:

do {
<sentencias>
} while <condicion>;

La secuencia de sentencias siempre se ejecuta al menos una vez, al contrario que en el bucle anterior. Notar que la condición termina con un ‘;’.

También debemos modificar las variables que afectan a la condición en la secuencia de sentencias del cuerpo del bucle o de lo contrario se puede entrar en un
bucle infinito.
...
int numero;
do {
System.out.print("Introduce un número entre 0 y 10 (ambos inclusive): ");
numero = Entrada.entero();
} while (numero < 0 || numero > 10);
System.out.println("El número introducido es: " + numero);
...

for: Este tipo de bucle se utiliza para repetir una secuencia de sentencias un número determinado de veces, en su formato más general. Pero, como veremos,
también se puede utilizar para otros menesteres, aunque para ello os aconsejo utilizar uno de los anteriores. Su sintaxis es:
for (<expresionInicializacion> ; <expresionCondicinal> ; <expresionIncremento>) {
<sentencias>
}

Primero se evalúa expresionInicializacion. Esta expresión es opcional. En su formato más genérico, aquí inicializaremos la variable de control del bucle.
También puede haber varias expresiones separadas por ,.

Luego se evalúa expresionCondicinal que debe ser una expresión lógica. Si evalúa a falso el bucle termina y en caso contrario se ejecutan la secuencia de
sentencias. También es opcional y si no existiese sería como si evaluase a true y estaríamos hablando de un bucle infinito.

Cuando se haya terminado de ejecutar la secuencia de sentencias, se evalúa expresionIncremento y se vuelve a evaluar expresionCondicional para volver a
ejecutar o no la secuencia de sentencias. expresionIncremento también es opcional y también podría haber varias expresiones separadas por ,.
...
for (int i = 1; i <= 10; i++) {
System.out.println("2 * " + i + " = " + 2 * i);
}
...

...
int numero;
System.out.print("Introduce un número entre 0 y 10 (ambos inclusive): ");
numero = Entrada.entero();
for (; numero < 0 || numero > 10;) {
System.out.println("ERROR: El número debe estar comprendido entre 0 y 10 (ambos inclusive)");
System.out.print("Por favor vuelve a introducir un número válido: ");
numero = Entrada.entero();
}

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 10/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
System.out.println("El número introducido es: " + numero);
...

Sentencias que rompen el flujo del programa

continue, break, goto:Son sentencias que no se deben utilizar ya que rompen el flujo del programa de una forma inadecuada y que se pueden evitar mediante
otros métodos más ortodoxos. La excepción a lo dicho, sería el uso de break en una sentencia case. Por tanto, no hablaré ni siquiera de ellas.

Sentencias de control de errores: Son sentencias que permiten controlar si se ha producido un error inesperado en el programa y actuar en consecuencia para
que el programa no termine de forma inesperada. Es a lo que se llama excepciones en java y que veremos con detalle en otro apartado.

Ejercicios
Sentencias secuenciales

Asignar valor a una variable y mostrarlo

Escribir un programa java que le asigne un valor cualquiera a una variable entera y lo muestre por pantalla.

Lectura/Escritura de un número entero

Escribir un programa java que lea un número entero por teclado y nos lo muestre por pantalla.

Mostrar el doble de un número real

Escribir un programa java que lea un número real por teclado y nos muestre por pantalla el doble del mismo.

Mostrar el cuadrado de un número real doble

Escribir un programa java que lea un número real doble por teclado y nos muestre por pantalla el cuadrado del mismo.

Hallar el perímetro de un rectángulo

Escribir un programa java que lea por teclado la base y la altura de un rectángulo y nos muestre por pantalla el perímetro del mismo.

Calcular el área de un círculo

Escribir un programa java que lea por teclado el radio de un círculo y nos muestre por pantalla el área del mismo.

Sentencias condicionales

Valor absoluto de un número

Escribir un programa java que lea por teclado un número y nos muestre por pantalla el valor absoluto del mismo.

Número par o impar

Escribir un programa java que lea por teclado un número y nos indique por pantalla si es par o impar.

Número positivo o negativo

Escribir un programa java que lea por teclado un número y nos indique por pantalla si es positivo o negativo.

Número entre 0 y 100

Escribir un programa java que lea por teclado un número real y nos indique por pantalla si éste se encuentra entre 0 y 100, ambos inclusive.

Calificación obtenida

Escribir un programa java que lea por teclado una calificación y nos indique si estamos aprobados o suspensos o si la calificación no es una calificación
correcta.

Números iguales

Escribir un programa java que lea por teclado dos números enteros y nos indique si son iguales o no.

Ordenar dos números

Escribir un programa java que lea por teclado dos números reales y nos los muestre ordenados de mayor a menor.

Ordenar tres números

Escribir un programa java que lea por teclado tres números reales y nos los muestre ordenados de mayor a menor.

Sentencias repetitivas

Tabla de multiplicar

Escribir un programa java que lea por teclado un número entero y nos muestre la tabla de multiplicar de dicho número.

Numero positivo

Escribir un programa java que lea por teclado un número entero positivo y nos lo muestre.

Sumar números

Escribir un programa java que lea por teclado números enteros hasta que se introduzca un cero muestre por pantalla la suma de los mismos.

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 11/12
9/7/23, 20:22 ies-al-andalus.github.io/Programacion/introduccionJava/
Media números

Escribir un programa java que lea por teclado números enteros hasta que se introduzca un cero muestre por pantalla la media de los mismos.

Ejercicios variados

Número perfecto

Escribir un programa java que lea un número entero mayor que cero por teclado y nos informe si dicho número es perfecto o no. Un número es perfecto si es
igual a la suma de sus divisores.

Número cifras

Escribir un programa java que lea un número entero mayor que cero por teclado y nos informe de la cantidad de cifras que posee dicho número. Para ello sólo
debes utilizar operaciones aritméticas.

Descomponer cifras

Escribir un programa java que lea un número entero mayor que cero por teclado y nos muestre en cada línea las cifras del mismo en orden de izquierda a
derecha. Para ello sólo debes utilizar operaciones aritméticas.

Descomponer cifras reverso

Escribir un programa java que lea un número entero mayor que cero por teclado y nos muestre en cada línea las cifras del mismo en orden de derecha a
izquierda. Para ello sólo debes utilizar operaciones aritméticas.

Mostrar reverso

Escribir un programa java que lea un número entero mayor que cero por teclado y nos muestre el reverso del mismo. Para ello sólo debes utilizar operaciones
aritméticas.

Mostrar reverso sin modificar

Escribir un programa java que lea un número entero mayor que cero por teclado y nos muestre el reverso del mismo, pero sin modificar el número introducido.
Para ello sólo debes utilizar operaciones aritméticas.

Capicúa

Escribir un programa java que lea un número entero mayor que cero por teclado y nos indique si dicho número es capicúa o no (un múmero es capicúa si su
reverso y él son iguales). Para ello sólo debes utilizar operaciones aritméticas.

Menú

Escribir un programa java que muestre un menú con tres opciones (Abrir, Cerrar y Salir) y una última para salir. Nos pida que elijamos una opción correcta y
nos informe de la opción elegida.

Sumar5Restar2

Escribir un programa java que lea 2 números enteros entre 0 y 50 (ambos inclusive). Al menor comenzará a sumarle 5 y al mayor comenzará a restarle 2. Los
resultados los irá mostrando en pantalla hasta que el menor sea mayor que el mayor.

Tiradas monedas

Escribir un programa java que simule la tirada de dos monedas. El programa pedirá por teclado el número de veces a tirar las monedas (entre 1 y 50). Mostrará
el resultado de cada tirada y luego nos informará del número de caras, del número de cruces, de el número máximo de caras dobles consecutivas y del número
máximo de cruces dobles consecutivas, que han salido.

Programación mantenido por IES-Al-Andalus

Ver en GitHub

https://ies-al-andalus.github.io/Programacion/introduccionJava/ 12/12
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

Programación
Repositorio sobre programación en java del IES Al-Ándalus
Introducción a la programación orientada a objetos en Java
Programación orientada a objetos
Clases en Java
Objetos en Java
Excepciones
Paquetes
Enumerados
Ejercicios

Introducción a la programación orientada a objetos en Java


En este apartado encontrarás una breve descripción sobre los conceptos en los que se sustenta la programación orientada a objetos. Seguidamente pasaré a mostrar
cómo definir, declarar, utilizar, etc. clases y objetos.

Al igual que en otros apartados, el objetivo principal es que tengáis una amplía gama de ejercicios, con sus respectivas posibles soluciones.

José Ramón Jiménez Reyes

@JRJimenezReyes

joseramon.jimenez@iesalandalus.org

Contenidos

Programación orientada a objetos


Clases en Java
Objetos en Java
Excepciones
Paquetes
Enumerados
Ejercicios

Programación orientada a objetos


La programación orientada a objetos es un paradigma de programación en el que un programa es un conjunto de objetos que se relacionan entre sí. Esta forma de
resolver un problema se adecúa más a lo que nos encontramos en la vida real y por eso la abstracción es más fácil.

La programación orientada a objetos se basa en una serie de conceptos o características:

Abstracción: permite centrarnos en las propiedades de los tipos de datos y no en su implementación.

Encapsulación: permite agrupar en un mismo módulo el estado o estructura de un tipo de dato y su comportamiento.

Ocultación de la información: permite indicar a qué propiedades de un tipo de dato tendremos acceso y a cuáles no.

Modularidad: permite descomponer el problema en componentes que pueden ser combinados entre sí para llegar a la solución del mismo.

Polimorfismo: permite que un tipo de dato pueda hacer referencia a diferentes tipos de dato.

Herencia: permite definir tipos de dato a partir de otros ya definidos y que el comportamiento de éstos se base en los primeros.

Clases en Java
Una clase es el molde que define cómo será el nuevo tipo de dato que se está creando. Además una clase también define el comportamiento que tendrán todos los
objetos que pertenezcan a dicha clase.

Por tanto, una clase se podrá definir por su estado o estructura y por su comportamiento. El estado vendrá dado por los atributos de la clase y el comportamiento
por los métodos de la misma. El comportamiento para todos los objetos de una clase es el mismo, lo que varía es el estado de cada objeto.

Por ejemplo, imagina que vamos a crear un videojuego en el que nos cuentan que los personajes del juego tendrán un nombre, un nivel de energía y un
color. Por ahora, nos comentan también que los personajes pueden chocar con otros personajes o con paredes y que eso les hará perder energía, pero que
pueden charlar con otros personajes lo que les podrá hacer aumentar su energía. Por lo que se podría modelar un personaje con una clase como la que se
muestra en el diagrama, en la que podemos distinguir su estado y su comportamiento.

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 1/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

Como se puede apreciar hay un apartado en el diagrama en el que se muestran los atributos y otro en el que se muestran los métodos (podrás apreciar
que aparece un método adicional que se llama igual que la clase y en el que nos detendremos un poco más adelante).

En Java podemos definir una clase utilizando la siguiente sintaxis:


[modificadores] class NombreClase [herencia] [interfaces]{
[atributos]
[metodos]
}

Aunque modificadores hay varios, por ahora simplemente nos quedaremos con public, que indica que la clase será visible desde todas las partes del código.

De la parte de la herencia e interfaces ya hablaremos en otro apartado más detenidamente.

Una clase debe tener un nombre significativo y debe ir almacenada en un fichero con extensión .java con el mismo nombre de la clase (esto es debido a que la clase
es pública, más adelante veremos otros casos al respecto). Recuerda que para nombrar las clases utilizábamos identificadores cuya inicial fuese una mayúscula y si
era la composición de varias palabras, cada una de ellas comenzaba en mayúscula.

Por tanto, podremos definir en java nuestra clase del ejemplo, de la siguiente forma:

public class Personaje {

Atributos

Los atributos definen la estructura de datos que formará la clase.

Para declarar los atributos utilizaremos la siguiente sintaxis:


[modificadoresAcceso][modificadoresContenido] tipo nombre;

Los modificadores de acceso son los que permitirán indicar el nivel de ocultación de la información para dichos atributos. Podemos encontrarnos los siguientes:

public: indica que cualquier clase tiene acceso a dicho atributo.


protected: indica que cualquier subclase de ésta tendrá acceso a dicho atributo y también las clases que pertenezcan al mismo paquete que la clase que
estamos definiendo.
private: indica que el atributo sólo será accesible desde la clase que estamos definiendo.
por omisión: si no indicamos ningún modificador de acceso, estaremos indicando que el nivel de acceso es a nivel de paquete y sólo podrán acceder a este
atributo las clases que pertenezcan al mismo paquete que la clase que estamos definiendo.

En el siguiente diagrama de clases se muestra cómo se representa la visibilidad de cada atributo:

Los modificadores de contenido son aquellos que le dan un sentido especial al contenido de ese atributo. Podemos encontrar:

static: indica que el atributo será de clase y no de instancia. Lo veremos más adelante.
final: indica que el valor del atributo no se puede cambiar.

Existe algún modificador más que no me detendré ni a nombrar, por ahora.

Ahora añadimos los atributos a la clase y quedaría así:


public class Personaje {

private String nombre;


private int energia;
private String color;

Métodos

Los métodos son los encargados de realizar operaciones que sean aplicables a los objetos de la clase. Además, para conservar el principio de ocultación, deberían ser
los únicos que modificasen el valor de los atributos.

Los métodos contarán con una cabecera y un cuerpo. La cabecera será la declaración del método en sí, en la que indicaremos entre otros: el nivel de acceso, el valor
devuelto, el nombre, la lista de parámetros. El cuerpo será el código que implemente dicho método.

Para declarar un método debemos utilizar la siguiente sintaxis:


https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 2/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
[modificadoresAcceso][modificadoresContenido] valorDevuelto nombre([listaParametros])[excepciones]

Los modificadores de acceso son iguales que para los atributos y tienen el mismo significado.

Los modificadores de contenido son casi iguales que para los atributos pero su significado cambia:

static: indica que el método es un método de clase y no de instancia. Lo veremos más adelante.
final: indica que el método no puede ser sobreescrito en la cadena de herencia. Lo veremos en otro apartado.
abstract: indica que es un método abstracto y que no tiene implementación. Lo veremos en otro apartado.

La lista de parámetros es una lista de declaraciones separadas por comas, es decir, de tipos de dato y nombre separados por coma. Un método también puede aceptar
un número variable de parámetros del mismo tipo mediante la construcción varargs que tampoco me detendré a mencionar, ya que no es más que una forma
transparente de pasar como parámetro un array.

Del apartado [excepciones] ya hablaremos algo más adelante.

Los métodos que devuelven algo distinto de void deben acabar con una sentencia return valor y devolverán un valor del mismo tipo del declarado.

Los métodos se pueden sobrecargar, es decir, definir varios métodos que tengan el mismo identificador pero con distinta lista de parámetros (sin tener en cuenta el
valor devuelto). Un uso muy común de sobrecarga de métodos es en el caso de los constructores.

En el siguiente código mostramos nuestra clase con dos primeras implementaciones de los métodos chocar y charlar:
public class Personaje {

private String nombre;


private int energia;
private String color;

public void chocar(int posiblePerdida) {


energia -= posiblePerdida;
}

public void charlar(int posibleGanancia) {


energia += posibleGanancia;
}

Constructores

Los constructores son un tipo de método especial que se llaman igual que la clase y los únicos que no devuelven ningún valor. Su modificador de acceso suele ser
public. Estos métodos son los encargados de asignar valores iniciales a los atributos, es decir, de establecer el estado inicial del objeto.

Podemos distinguir tres:

Por defecto: es aquel constructor que no acepta parámetros y que inicializará el valor de los atributos a un valor por defecto conveniente para la clase.
Con parámetros: Son los que aceptan parámetros y dependiendo de estos parámetros los atributos tomarán unos valores u otros. Generalmente los parámetros
suelen ser valores iniciales que queremos darle a alguno de los atributos.
Copia: Es aquel constructor que es capaz de realizar una copia del objeto que se le pasa como parámetro, que será del mismo tipo que la clase que estamos
definiendo. Cuando hablemos del problema de las referencias volveremos a hablar de este tipo de constructores y en qué consiste la copia profunda en
contraposición con la copia superficial.

Si nosotros no definimos ningún constructor, el compilador generará uno por defecto por nosotros. Recuerda el método que aparecía en el diagrama de clases que se
llamaba igual que la clase.

Un constructor puede llamar a otro constructor. Por ejemplo, un constructor con parámetros, primero podría querer llamar al constructor por defecto y luego realizar
otras acciones. Para ello se utiliza la llamada a this() que llamaría al constructor por defecto o si indicamos los parámetros adecuados, a otro constructor. La
llamada a this() debe ser la primera línea de la implementación del constructor.

En los constructores (y en muchos otros métodos) es típico que el parámetro del método se llame igual que el atributo cuyo valor queremos establecer. Con esto
surge un problema ya que dentro del método cuando utilicemos dicho nombre, nos estaremos refiriendo al parámetro y no al atributo. Para eliminar la ambigüedad
podemos utilizar la palabra clave this que se refiere a la clase en cuestión y mediante la utilización del operador . podremos acceder al atributo deseado:

Si añadimos los constructores a nuestro ejemplo de Personaje, el código podría quedarnos así:

public class Personaje {

private String nombre;


private int energia;
private String color;

public Personaje() {
nombre = "Personaje";
energia = 100;
color = "Rojo";
}

public Personaje(String nombre) {


this();
this.nombre = nombre;
}

public void chocar(int posiblePerdida) {


energia -= posiblePerdida;
}

public void charlar(int posibleGanancia) {


energia += posibleGanancia;
}

Métodos de acceso y modificación

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 3/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
Son aquellos métodos que nos permiten consultar y/o establecer el valor de los atributos.

Los métodos de consulta se nombran como getNombre donde Nombre es el nombre del atributo que queremos consultar. En el caso de que sea un atributo de tipo
boolean se utiliza isNombre para realizar la consulta.

Los métodos de modificación se nombran como setNombre donde Nombre es el nombre del atributo que queremos modificar.

Cierto es que estos métodos podrían tener el nombre que quisiéramos, pero por convención utilizaremos estos nombres.

Para cumplir con el principio de ocultación, la norma general es:

Definiremos los atributos con una visibilidad private.


Pensaremos qué atributos queremos que sean accesibles desde fuera y creamos métodos de consulta para los mismos con una visibilidad public.
Generalmente, para todos los atributos querremos que se pueda consultar su valor.
Pensaremos qué atributos queremos que se puedan modificar, pasándole un nuevo valor. Ten en cuenta que habrá atributos que no tenga mucho sentido que
podamos cambiar a nuestro antojo ya que: no tiene sentido cambiar dicho atributo una vez creado el objeto, depende de otros atributos que se pueda modificar
o no, al modificar este atributo también habría que modificar otros para que nuestra clase guarde la consistencia, etc. Una vez decididos qué atributos
queremos que se puedan modificar crearemos sus métodos de modificación con una visibilidad public.
Habrá atributos para los que no queramos crear métodos de modificación pero para los que tenemos que hacer ciertas validaciones antes de modificar el valor
de dicho atributo. Para estos atributos es una buena práctica crear métodos de modificación con visibilidad private que usarán otros métodos, como pudiera
ser el constructor, para que lleve a cabo dichas validaciones. Es una buena costumbre llamar a los métodos set para modificar el valor de los atributos.

De esta forma estamos aislando a los clientes de nuestra clase de los cambios en la estructura de la misma.

Siguiendo con nuestro ejemplo del videojuego, debemos proporcionar a la clase de métodos de acceso para los atributos, pero sin embargo los métodos
de modificación de los mismos no tienen sentido para todos los atributos, ya que no tiene sentido cambiar el nombre de un personaje una vez creado y
tampoco tiene sentido modificar a nuestro antojo la energía de un personaje ya que se hará por medio de otros métodos. Por tanto, nuestro diagrama de
clases queda como sigue:

Y nuestra clase quedaría tal como sigue:


public class Personaje {

private String nombre;


private int energia;
private String color;

public Personaje() {
nombre = "Personaje";
energia = 100;
color = "Rojo";
}

public Personaje(String nombre) {


this();
this.nombre = nombre;
}

public String getNombre() {


return nombre;
}

public int getEnergia() {


return energia;
}

public String getColor() {


return color;
}

public void setColor(String color) {


this.color = color;
}

public void chocar(int posiblePerdida) {


energia -= posiblePerdida;
}

public void charlar(int posibleGanancia) {


energia += posibleGanancia;
}

Miembros estáticos

Hasta ahora hemos visto los miembros (atributos y métodos) de instancia, es decir, los miembros particulares de cada uno de los objetos que se creen de dicha clase.
Pero hay situaciones en las que debemos compartir información entre todos los objetos de una clase o que queremos utilizar algún método de la clase sin necesidad

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 4/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
de tener que instanciar un objeto de la misma. Estos son los miembros de clase.

Los atributos de clase son atributos compartidos por todos los objetos de la clase y en su definición utilizamos el modificador static. Por ejemplo, imaginemos que
queremos saber en cada momento cuántos objetos se han creado de una clase. Para ello definiremos un atributo de clase que en el constructor de la misma se irá
incrementando. Para los atributos de clase podemos definir los métodos de acceso y/o modificación según convenga.

En java no existen las constantes como tal, pero podemos conseguir el mismo efecto declarando dicho atributo como static final. Por tanto, sería un atributo de
clase que no se puede modificar. Estos atributos no tiene sentido que tengan métodos de acceso y mucho menos de modificación ya que no está permitida. Es normal
utilizar las constantes para indicar los valores por defecto que utilizaremos en los constructores, entre otros usos. Este tipo de atributos hay que inicializarlos en la
declaración o en el constructor y ya no se le podrá cambiar el valor.

Los métodos de clase son métodos que no se aplican sobre objetos, sino sobre la clase en sí. Para definir estos métodos debemos utilizar el modificador static en su
declaración. Para invocar estos métodos no es necesario instanciar ningún objeto de la clase. Estos métodos se invocan sobre la clase (su nombre). Aunque no lo
creas ya has utilizado métodos de clase sin saberlo: Math.random(), Entrada.entero(), etc. Otra aspecto importante sobre estos métodos es que en su cuerpo sólo
pueden hacer referencia a miembros de clase y en ningún caso a miembros de instancia.

Es común declarar clases de utilidades que sólo tienen métodos de clase y atributos de clase. Por ejemplo, la clase Math o la clase Entrada. Dado que no vamos a
instanciar objetos de dichas clases, debemos evitar que el compilador nos cree el constructor por defecto y para ello definiremos un constructor privado con un
simple comentario en su cuerpo y así nos aseguramos que nadie va a instanciar un objeto de nuestra clase de utilidades. Esto puedes verlo en la implementación de la
clase Entrada que os proporcioné en el apartado anterior para realizar la entrada por teclado.

Supongamos que para nuestro videojuego queremos llevar la cuenta de los personajes que hemos ido creando y que podamos consultarlo. Además, en el
constructor por defecto asignaremos un nombre que haga referencia al número de personaje. También queremos utilizar algunas constantes para los
valores iniciales. Veamos cómo quedaría nuestro diagrama de clases y el código de nuestra clase. Podéis apreciar en el diagrama que los miembros
estáticos aparecen subrayados (además, he hecho que se muestren con un color diferente ya que el subrayado no se aprecia lo suficiente).

public class Personaje {

private static final int ENERGIA_INICIAL = 100;


private static final String COLOR_INICIAL = "Rojo";
private static final String PREFIJO_NOMBRE = "Personaje";

private static int numPersonajes = 0;

private String nombre;


private int energia;
private String color;

public Personaje() {
numPersonajes++;
nombre = PREFIJO_NOMBRE + numPersonajes;
energia = ENERGIA_INICIAL;
color = COLOR_INICIAL;
}

public Personaje(String nombre) {


this();
this.nombre = nombre;
}

public String getNombre() {


return nombre;
}

public int getEnergia() {


return energia;
}

public String getColor() {


return color;
}

public void setColor(String color) {


this.color = color;
}

public static int getNumPersonajes() {


return numPersonajes;
}

public void chocar(int posiblePerdida) {


energia -= posiblePerdida;
}

public void charlar(int posibleGanancia) {


https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 5/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
energia += posibleGanancia;
}

Relación de clientela

Hasta ahora hemos declarado atributos de tipos primitivos o a lo sumo String. Pero una clase también puede tener atributos cuyo tipo es otra clase. Cuando una clase
X declara atributos cuyo tipo es de otra clase Y, entonces se dice que la clase X es cliente de la clase Y.

A veces nos ponemos a diseñar una clase y vemos que tenemos demasiados atributos de tipos primitivo. Generalmente esto nos está indicando que deberíamos crear
otra clase que contenga algunos de dichos atributos relacionados y así hacer la primera clase cliente de la segunda.

Cuando tenemos un atributo cuyo tipo es de otra clase debemos tener en cuenta que en el constructor habrá que crearlo o de lo contrario su valor será null.

Más adelante hablaremos del problema del aliasing que debemos tener muy en cuenta.

También hacer notar que en los diagramas de clase, estos atributos cuyo tipo son de otra clase no aparecen como tal en la lista de atributos. La relación de cliente se
expresa en estos diagramas mediante una flecha desde una clase a la otra. La flecha se etiqueta con el nombre del atributo y la cardinalidad, que por ahora será 0..1
ya que se trata de un atributo simple y no es un array o una colección, en cuyo caso podría poner el límite superior del array o * si es que no tiene límite superior.

Imaginemos que ahora nos dicen que nuestro personaje ocupará una posición dentro la pantalla, que podrá ir cambiando conforme se vaya moviendo.
Podríamos añadir dos atributos nuevos a nuestra clase que indiquen la posición X e Y de nuestro personaje. Sin embargo, otra solución más acertada
sería crear una clase llamada Posicion que contendrá como atributos la posición X e Y y luego hacer que nuestra clase Personaje sea cliente de la clase
Posicion. Como queremos que nuestro personaje se pueda mover, permitiremos modificar la posición en X y en Y, por lo que haremos que los métodos
de modificación sean públicos para poder modificar dichas coordenas desde fuera de la clase (además esto lo hacemos por motivos didácticos para luego
ejemplificar algunos problemas que pueden surgir de este diseño). Para que nuestro personaje se pueda mover debemos implementar un método llamado
mover que recibirá como parámetros el incremento en la coordenada X y el incremento en la coordenada Y, y que modifique la posición de nuestro
personaje en consecuencia. Tendremos unas constantes que marcarán los límites de la coordenada X y la Y y si intentamos asignar un valor que no esté
dentro de esos límites, no lo permitirá y le asignará el valor mínimo, aunque en principio no deberíamos dejar hacerlo, pero eso lo dejamos para
corregirlo cuando veamos las excepciones. Veamos cómo quedaría el diagrama de clases y el código de ambas clases.

Posicion.java

public class Posicion {

private static final int MIN_X = 0;


private static final int MAX_X = 100;
private static final int MIN_Y = 0;
private static final int MAX_Y = 100;

private int x;
private int y;

public Posicion() {
x = MIN_X;
y = MIN_Y;
}

public Posicion(int x, int y) {


setX(x);
setY(y);
}

public Posicion(Posicion posicion) {


setX(posicion.getX());
setY(posicion.getY());
}

public int getX() {


return x;
}

public void setX(int x) {


if (x < MIN_X || x> MAX_X) {
this.x = MIN_X;
} else {
this.x = x;
}
}

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 6/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

public int getY() {


return y;
}

public void setY(int y) {


if (y < MIN_Y || y > MAX_Y) {
this.y = MIN_Y;
} else {
this.y = y;
}
}

Personaje.java

public class Personaje {

private static final int ENERGIA_INICIAL = 100;


private static final String COLOR_INICIAL = "Rojo";
private static final String PREFIJO_NOMBRE = "Personaje";

private static int numPersonajes = 0;

private String nombre;


private int energia;
private String color;
private Posicion posicion;

public Personaje() {
numPersonajes++;
nombre = PREFIJO_NOMBRE + numPersonajes;
energia = ENERGIA_INICIAL;
color = COLOR_INICIAL;
posicion = new Posicion();
}

public Personaje(String nombre) {


this();
this.nombre = nombre;
}

public Personaje(String nombre, Posicion posicion) {


this(nombre);
this.posicion = posicion;
}

public String getNombre() {


return nombre;
}

public int getEnergia() {


return energia;
}

public String getColor() {


return color;
}

public void setColor(String color) {


this.color = color;
}

public Posicion getPosicion() {


return posicion;
}

public void chocar(int posiblePerdida) {


energia -= posiblePerdida;
}

public void charlar(int posibleGanancia) {


energia += posibleGanancia;
}

public void mover(int x, int y) {


posicion.setX(posicion.getX() + x);
posicion.setY(posicion.getY() + y);
}

Objetos en Java
Un objeto es una instancia particular de una clase que se crea en tiempo de ejecución. Podemos tener tantos objetos como sea necesario, ya sean de diferentes clases
o de la misma. Dicha instancia del objeto es una representación en memoria del estado de dicho objeto, es decir, de los valores particulares de cada atributo de dicho
objeto.

Lo primero que debemos hacer es declarar el objeto. Dicha declaración se hace de la misma manera que declarábamos cualquier otra variable de tipo primitivo,
pero en vez del tipo primitivo utilizamos el nombre de la clase a la que pertenece dicho objeto.
Personaje miPersonaje;

Al declarar el objeto, lo que realmente estamos haciendo es indicar que dicho identificador será una referencia a un objeto de esa clase. En ese momento, dicha
referencia tiene el valor null, es decir, aún no apunta a ninguna posición de memoria.

Seguidamente debemos crear el objeto. Para crear el objeto utilizaremos el operador new seguido por uno de los constructores de la clase.

miPersonaje = new Personaje("Calamardo");


https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 7/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
Con la creación del objeto lo que estamos haciendo es reservar un espacio de memoria para albergar un objeto de dicha clase y hacer que la referencia a dicho objeto
apunte a dicha posición de memoria. En la siguiente imagen lo puedes ver de forma gráfica.

Una vez tenemos declarada la referencia y creado el objeto, ya podemos utilizar dicho objeto. Para acceder a los miembros para los que su visibilidad nos lo permita,
utilizamos el identificador de la referencia, seguido de un . y seguido del miembro al que queremos acceder, que según hemos visto, deberían ser métodos ya que el
acceso a los atributos no está indicando en la mayoría de los casos.

miPersonaje.chocar(20);
int energiaAux = miPersonaje.getEnergia();

Mostrar el estado de un objeto

Si queremos mostrar el estado de un objeto podemos utilizar, por ejemplo, la sentencia System.out.println pasándole la referencia a dicho objeto. Sin embargo, si
hacemos esto se nos mostrará información sobre la clase a la que pertenece el objeto y una referencia a su identificador.

Generalmente, nos convendrá que a la hora de realizar esta operación se muestre el estado del objeto, es decir, el valor de cada uno de sus atributos. Para ello, la
clase simplemente debe sobreescribir el método toString heredado de Object para que dicha representación se adecúe a nuestras necesidades. Este método no recibe
parámetros y devuelve una cadena con la representación del mismo.

Ahora queremos que nuestra clase Posicion permita mostrar el estado de los objetos de la misma, informándonos del valor de x y de y. Para ello
simplemente sobreescribiremos el método toString para que se adecúe a estas premisas.
...
@Override
public String toString() {
return String.format("x=%s, y=%s", x, y);
}

Una vez hecho esto, ya podremos mostrar es estado de los objetos de la clase Posicion

...
Posicion miPosicion = new Posicion();
System.out.println(miPosicion); //Muestra: x=0, y=0

Referencias

Cuando asignamos una referencia a otra, no estamos copiando el contenido de una en otra, simplemente estamos igualando ambas referencias al mismo objeto.

Por ejemplo, cuando nosotros ejecutamos el siguiente código:

...
Posicion posicion1 = new Posicion();
Posicion posicion2;
posicion2 = posicion1;

Lo que realmente está ocurriendo es lo siguiente:

Este uso de las referencias tienen algunas ventajas como la compartición o la posibilidad de crear estructuras recursivas, pero tiene un inconveniente: el aliasing.

Veamos en qué consiste este problema, analizando el siguiente código:

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 8/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
...
Posicion posicion1 = new Posicion(10, 10);
Posicion posicion2;
posicion2 = posicion1;
System.out.println(posicion1); //Muestra: x=10, y=10
posicion2.setX(20);
System.out.println(posicion1); //Muestra: x=20, y=10

Como se puede apreciar, simplemente hemos modificado la coordenada x de la referencia posicion2, pero dicha modificación también ha afectado a la referencia
posicion1. Si lo pensamos, es normal ya que ambas referencias apuntan al mismo objeto y cualquier modificación afectará a ambas referencias.

El aliasing ocurre cuando estamos trabajando con clases mutables, es decir, se puede modificar su estado. Esto no ocurriría si la clase Posicion fuese inmutable y eso
se podría conseguir haciendo que sus método setX no fuesen públicos (o no estuviesen implementados).

Dado que en nuestro ejemplo, la clase Posicion es mutable, debemos tenerlo muy en cuenta ya que a veces podemos tener efectos no deseados. Por ejemplo, en un
método de acceso de una clase, que accede a un atributo que es una referencia a otro objeto, si devolvemos la referencia estaremos comprometiendo la integridad ya
que desde fuera se podrían cambiar los valores del objeto al que apunta. Veámoslo con un ejemplo:

...
Personaje miPersonaje = new Personaje("Calamardo");
Posicion miPosicion = miPersonaje.getPosicion();
System.out.println(miPersonaje.getPosicion()); //Muestra: x=0, y=0
miPosicion.setX(10);
System.out.println(miPersonaje.getPosicion()); //Muestra: x=10, y=0

Este mismo caso se puede dar cuando pasamos una referencia al constructor o a un método de modificación y simplemente igualamos la referencia.
...
Posicion miPosicion = new Posicion(10, 10);
Personaje miPersonaje = new Personaje("Calamardo", miPosicion);
System.out.println(miPosicion); //Muestra: x=10, y=10
miPersonaje.mover(10, 0);
System.out.println(miPosicion); //Muestra: x=20, y=10

Por tanto, aunque depende del problema a resolver, lo normal es que creemos nuevas instancias partiendo de la pasada o que las devolvamos (esto siempre que las
referencias no sean a objetos inmutables, como es el caso de la clase String). Para ello, es muy común utilizar el constructor copia de la clase, del que hemos
hablado muy por encima.

El constructor copia lo que hará es crear un nuevo objeto con los valores de los atributos igualados a los de la referencia del objeto pasado como argumento. Si
simplemente copiamos los valores, estaremos creando lo que se llama copia superficial y si la clase es cliente de otra clase mutable, podríamos encontrarnos con
este problema.
public Personaje(Personaje personaje) {
nombre = personaje.getNombre();
energia = personaje.getEnergia();
color = personaje.getColor();
posicion = personaje.getPosicion();
}

Como se puede ver, para el atributo posicion hemos igualado la referencia, ya que el método getPosicion devolvía dicha referencia.

Para solucionarlo, utilizaremos la copia profunda que en vez de igualar referencias, cree nuevos objetos e iguale a dichos objetos recién creados.
public Personaje(Personaje personaje) {
nombre = personaje.getNombre();
energia = personaje.getEnergia();
color = personaje.getColor();
posicion = new Posicion(personaje.posicion);
}

Con esto habríamos solucionado el problema, pero seguimos devolviendo la referencia de la posición en el método de consulta y nos quedamos con la referencia en
el metodo de establecimiento, y eso deberíamos arreglarlo.

Por todo lo comentado anteriormente, si nosotros utilizamos el operado == para comparar la igualdad de dos referencias, nos devolverá true si ambas apuntan al
mismo objeto y false si apuntan a objetos diferentes aunque el estado de ambos sea idéntico.

Para solucionar este otro problema debemos utilizar el método equals. Lo normal es que para cada clase nosotros sobreescribamos este método. Por otro lado, para
ser consecuentes, java internamente puede utilizar otro método llamado hashCode que devuelve un número único para cada objeto, y que para dos objetos con el
mismo estado devolverá el mismo número y para dos objetos diferentes deberá devolver números diferentes. La documentacion de java dice que equals y hashCode
deben comportarse de igual forma por consistencia, por lo que si sobreescribimos uno deberíamos sobreescribir el otro. Generalmente estos dos métodos los pueden
generar los IDEs por nosotros.

Ahora os muestro el código de ambas clases con los problemas de aliasing corregidos y con la implementación de equals y hashCode.

Posicion.java

import java.util.Objects;

public class Posicion {

private static final int MIN_X = 0;


private static final int MAX_X = 100;
private static final int MIN_Y = 0;
private static final int MAX_Y = 100;

private int x;
private int y;

public Posicion() {
x = MIN_X;
y = MIN_Y;
}

public Posicion(int x, int y) {


setX(x);
setY(y);
}

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 9/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

public Posicion(Posicion posicion) {


setX(posicion.getX());
setY(posicion.getY());
}

public int getX() {


return x;
}

public void setX(int x) {


if (x < MIN_X || x > MAX_X) {
this.x = MIN_X;
} else {
this.x = x;
}
}

public int getY() {


return y;
}

public void setY(int y) {


if (y < MIN_Y || y > MAX_Y) {
this.y = MIN_Y;
} else {
this.y = y;
}
}

@Override
public int hashCode() {
return Objects.hash(x, y);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Posicion)) {
return false;
}
Posicion other = (Posicion) obj;
return x == other.x && y == other.y;
}

@Override
public String toString() {
return String.format("x=%s, y=%s", x, y);
}

Personaje.java

import java.util.Objects;

public class Personaje {

private static final int ENERGIA_INICIAL = 100;


private static final String COLOR_INICIAL = "Rojo";
private static final String PREFIJO_NOMBRE = "Personaje";

private static int numPersonajes = 0;

private String nombre;


private int energia;
private String color;
private Posicion posicion;

public Personaje() {
numPersonajes++;
nombre = PREFIJO_NOMBRE + numPersonajes;
energia = ENERGIA_INICIAL;
color = COLOR_INICIAL;
posicion = new Posicion();
}

public Personaje(String nombre) {


this();
this.nombre = nombre;
}

public Personaje(String nombre, Posicion posicion) {


this(nombre);
setPosicion(posicion);
}

public Personaje(Personaje personaje) {


nombre = personaje.getNombre();
energia = personaje.getEnergia();
color = personaje.getColor();
posicion = new Posicion(personaje.posicion);
}

public String getNombre() {


return nombre;
}

public int getEnergia() {


return energia;
}

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 10/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

public String getColor() {


return color;
}

public void setColor(String color) {


this.color = color;
}

public Posicion getPosicion() {


return new Posicion(posicion);
}

private void setPosicion(Posicion posicion) {


this.posicion = new Posicion(posicion);
}

public void chocar(int posiblePerdida) {


energia -= posiblePerdida;
}

public void charlar(int posibleGanancia) {


energia += posibleGanancia;
}

public void mover(int x, int y) {


posicion.setX(posicion.getX() + x);
posicion.setY(posicion.getY() + y);
}

@Override
public int hashCode() {
return Objects.hash(color, energia, nombre, posicion);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Personaje other = (Personaje) obj;
return Objects.equals(color, other.color) && energia == other.energia && Objects.equals(nombre, other.nombre)
&& Objects.equals(posicion, other.posicion);
}

@Override
public String toString() {
return String.format("nombre=%s, energia=%s, color=%s, posicion=(%s)", nombre, energia, color, posicion);
}

Excepciones
Una excepción no es más que un error o un problema que ocurre durante la ejecución de nuestro programa. Cuando se produce esta situación anómala el flujo
normal del programa se interrumpe y el programa termina abruptamente. Nosotros, como buenos programadores, intentaremos por todos los medios que nuestro
programa sea robusto y que, por tanto, sea capaz de reaccionar adecuadamente ante estas situaciones excepcionales.

Imaginaos, por ejemplo, que queremos transferir un archivo por la red y lo queremos guardar en un archivo que se encuentra en un pendrive. Veamos qué situaciones
anómalas se pueden dar:

Que el origen de donde leeremos el fichero está malformado o el fichero no existe.


Que la conexión de red se pierda por algún motivo (alguién apaga la wifi, hay un problema en el router, etc.).
Que el pendrive está lleno y no tiene espacio para almacenar dicho fichero.
Que en medio de la escritura del fichero, alguien saca el pendrive.
Que no tenemos permiso de escritura en el pendrive.
Etc.

En java las excepciones están representadas por una clase. Las excepciones, al igual que toda clase, siguen una jerarquía de clases, que no voy a mostrar ya que ni
siquiera hemos hablado de herencia, aún. Uno de los métodos más comunes de las excepciones es el método getMessage que devuelve una cadena con un mensaje
explicativo sobre la causa de dicha excepción.

Dentro de estas situaciones anómalas podemos distinguir tres categorías:

Errores: son situaciones ante las que nosotros poco podemos hacer. Este tipo de situaciones quedan fuera de nuestro alcance y terminarán el programa. Por
ejemplo, que la JVM se queda sin memoria. Ante esto poco podemos hacer (además de revisar nuestro código para ver si podemos reducir el uso de memoria
de alguna forma). Por ello, no se consideran excepciones propiamente dichas. Todos los errores heredan de la clase Error.
Excepciones comprobadas: son situaciones que son típicas que ocurran al tratar con ciertos recursos como disco, red, etc. Estas excepciones debemos
tratarlas para evitar que nuestro programa termine abruptamente. Además, estamos obligados a tratarlas ya que si no, tendremos un error en tiempo de
compilación. Todas las excepciones comprobadas heredan de la clase Exception, pero no de RuntimeException. Este tipo de excepciones es obligatorio
tratarlas.
Excepciones no comprobadas: son situaciones que ocurren debido a errores que cometemos en la programación: intentar acceder a una posición de un array
que no existe (IndexOutOfBoundsException), intentar trabajar con un objeto nulo (NullPointerException), pasar un argumento no válido a un método
(IllegalArgumentException), etc. Todas estas excepciones heredan de RuntimeException. Este tipo de excepciones podemos tratarlas o no, según sea la
situación.

Tratamiento de una excepción

Cuando vamos a llamar a un método que pueda lanzar una excepción, encerraremos dicha llamada al método en un try. Seguida a este bloque, puede haber uno o
varios bloques catch. Y podemos terminar con un bloque finally. Veamos que significa todo esto:

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 11/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
En el bloque try se ejecuta la sentencia que puede lanzar una o varias excepciones. Si se lanza una excepción, el flujo se detendrá y pasaremos a los bloques
catch.
En cada uno de los bloques catch capturamos un tipo de excepción que se ha podido lanzar en el bloque try e indicamos cómo reaccionar ante dicha situación.
Cada sentencia catch va seguida por el nombre de la excepción ante la que queremos reaccionar.
El bloque finally es un bloque opcional que se ejecuta siempre, se haya producido alguna excepción o no. Se suele utilizar para cerrar recursos abiertos. A
partir de java SE 7, este bloque perdió su sentido original debido a la introducción de la sentencia try-with-resources que ya veremos en otros apartados.

try {
fichero = new FileInputStream(nombreArchivo);
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
}

Los bloques try se pueden anidar. Además en un bloque catch se pueden capturar varias excepciones separando sus nombre por medio del operador |.
try {
fichero = new FileInputStream(nombreArchivo);
aux = (byte) fichero.read();
} catch (IOException | FileNotFoundException e) {
System.out.println(e.getMessage());
}

Propagación de las excepciones

Hay veces en las que no sabemos muy bien cómo tratar la excepción, no es el momento de tratarla, etc. En estos casos lo que podemos hacer es dejar escapar la
excepción o, lo que es lo mismo, propagarla. Para ello en el método que no estemos tratando una excepción deberemos indicarle al compilador que ese método
puede propagar la excepción y que sea el código que invoca dicho método el encargado de tratarla. Para ello se utiliza la expresión throws seguido por el nombre o
nombres de las excepciones que dicho método puede propagar, en la cabecera del mismo.

public void leerFichero() throws IOException, FileNotFoundException

El tratamiento de las excepciones por tanto se puede delegar. Si una excepción se propaga desde el sitio donde se produjo y llega al método main y no es tratada, el
programa terminará abruptamente imprimiendo la pila de llamadas a métodos, empezando donde se produjo y mostrando todos los métodos por donde ha ido
pasando hasta llegar al método main. Esto es lo que siempre deberemos evitar.

Ni que decir tiene que todas las excepciones comprobadas deberemos o bien tratarlas o bien propagarlas.

Lanzamiento de excepciones

Nosotros podemos lanzar una excepción en el momento que deseemos. Esto generalmente se hace con las excepciones propias, es decir, con las que nosotros
definimos (esto lo veremos cuando hablemos de la herencia). Pero también se puede hacer con cualquier otro tipo de excepción para indicar que ha ocurrido un error
del tipo de dicha excepción. Por ejemplo, es muy común lanzar la excepción no comprobada IllegalArgumentException en nuestras clases, cuando pasamos
parámetros con valores incorrectos a nuestros métodos.

Para lanzar una excepción, simplemente tendremos que crear un nuevo objeto del tipo de la excepción y notificarlo con la sentencia throw.

if (objeto == null) {
throw new NullPointerException("El objeto pasado es nulo.");
}

Ahora que hemos visto el tratamiento de excepciones, queremos que nuestra clase Personaje pueda informar si se ha pasado algún valor incorrecto a los
métodos. Además, como os comenté también utilizaré métodos modificadores privados que se encargarán de realizar la comprobación y que se llamarán
en el constructor. Además queremos que el método mover lance una excepción comprobada OperationNotSupportedException cuando estemos intentando
mover la posición de nuestro personaje y éste sobrepase los límites establecidos.

Posicion.java

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 12/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
import java.util.Objects;

public class Posicion {

private static final int MIN_X = 0;


private static final int MAX_X = 100;
private static final int MIN_Y = 0;
private static final int MAX_Y = 100;

private int x;
private int y;

public Posicion() {
x = MIN_X;
y = MIN_Y;
}

public Posicion(int x, int y) {


setX(x);
setY(y);
}

public Posicion(Posicion posicion) {


if (posicion == null) {
throw new NullPointerException("No puedo copiar una posición nula.");
}
setX(posicion.getX());
setY(posicion.getY());
}

public int getX() {


return x;
}

public void setX(int x) {


if (x < MIN_X) {
throw new IllegalArgumentException("El valor de la x es menor que el mínimo permitido.");
} else if (x > MAX_X) {
throw new IllegalArgumentException("El valor de la x es mayor que el máximo permitido.");
}
this.x = x;
}

public int getY() {


return y;
}

public void setY(int y) {


if (y < MIN_Y) {
throw new IllegalArgumentException("El valor de la y es menor que el mínimo permitido.");
} else if (y > MAX_Y) {
throw new IllegalArgumentException("El valor de la y es mayor que el máximo permitido.");
}
this.y = y;
}

@Override
public int hashCode() {
return Objects.hash(x, y);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Posicion other = (Posicion) obj;
return x == other.x && y == other.y;
}

@Override
public String toString() {
return String.format("x=%s, y=%s", x, y);
}

Personaje.java

import java.util.Objects;

import javax.naming.OperationNotSupportedException;

public class Personaje {

private static final String PREFIJO_NOMBRE = "Personaje ";


private static final int ENERGIA_INICIAL = 100;
private static final String COLOR_INICIAL = "Rojo";
private static final int MIN_ENERGIA = 0;
private static final int MAX_ENERGIA = 100;

private static int numPersonajes = 0;

private String nombre;


private int energia;
private String color;
private Posicion posicion;

public Personaje() {
numPersonajes++;

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 13/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
nombre = PREFIJO_NOMBRE + numPersonajes;
energia = ENERGIA_INICIAL;
color = COLOR_INICIAL;
posicion = new Posicion();
}

public Personaje(String nombre) {


this();
setNombre(nombre);
}

public Personaje(String nombre, int energia, String color, Posicion posicion) {


this();
setNombre(nombre);
setEnergia(energia);
setColor(color);
setPosicion(posicion);
}

public Personaje(Personaje personaje) {


this();
if (personaje == null) {
throw new NullPointerException("No puedo copiar un personaje nulo.");
}
nombre = personaje.getNombre();
energia = personaje.getEnergia();
color = personaje.getColor();
posicion = new Posicion(personaje.posicion);
}

public String getNombre() {


return nombre;
}

private void setNombre(String nombre) {


if (nombre == null) {
throw new NullPointerException("El nombre del personaje no puede ser nulo.");
}
this.nombre = nombre;
}

public int getEnergia() {


return energia;
}

private void setEnergia(int energia) {


if (energia < MIN_ENERGIA) {
throw new IllegalArgumentException("El valor de la energia no puede ser menor que el mínimo establecido.");
} else if (energia > MAX_ENERGIA) {
throw new IllegalArgumentException("El valor de la energia no puede ser mayor que el máximo establecido.");
}
this.energia = energia;
}

public String getColor() {


return color;
}

public void setColor(String color) {


if (color == null) {
throw new NullPointerException("El color del personaje no puede ser nulo.");
}
this.color = color;
}

public Posicion getPosicion() {


return posicion;
}

public void setPosicion(Posicion posicion) {


if (posicion == null) {
throw new NullPointerException("La posición del personaje no puede ser nula.");
}
this.posicion = posicion;
}

public void chocar(int posiblePerdida) {


setEnergia(energia - posiblePerdida);
}

public void charlar(int posibleGanancia) {


setEnergia(energia + posibleGanancia);
}

public void mover(int x, int y) throws OperationNotSupportedException {


try {
posicion.setX(posicion.getX() + x);
posicion.setY(posicion.getY() + y);
} catch (IllegalArgumentException e) {
throw new OperationNotSupportedException("Movimiento no válido: " + e.getMessage());
}
}

@Override
public int hashCode() {
return Objects.hash(color, energia, nombre, posicion);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 14/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
if (getClass() != obj.getClass())
return false;
Personaje other = (Personaje) obj;
return Objects.equals(color, other.color) && energia == other.energia && Objects.equals(nombre, other.nombre)
&& Objects.equals(posicion, other.posicion);
}

@Override
public String toString() {
return String.format("nombre=%s, energia=%s, color=%s, posicion=(%s)", nombre, energia, color, posicion);
}

Paquetes
Hasta ahora, los programas que has realizado constaban de una sola clase o muy pocas. En este apartado hemos visto como poco a poco va creciendo el número de
clases a utilizar por un programa. Por lo que se necesita una forma de estructurar nuestras clases.

Para ello existen los paquetes, que no son más que agrupaciones de clases relacionadas. Al fin y al cabo esto no es más que diferentes directorios con sus clases. Pero
para que una clase pertenezca a un paquete no basta con colocarla en un determinado directorio, sino que la primera instrucción del fichero .java debe indicar el
paquete al que pertenece.

Los paquetes se pueden anidar unos dentro de otros.

package videojuego;
...

Para poder hacer referencia a una clase dentro de otra, debemos utilizar su nombre cualificado que es el nombre de la clase precedido por la ruta de los diferentes
paquetes a los que pertenece (comenzamos en la raíz de la jerarquía y vamos descendiendo), separados por ..

...
int i = org.iesalandalus.programacion.utilidades.Entrada.entero();

Pero esta nomenclatura es muy engorrosa. Además si tenemos muchos paquetes, unos dentro de otros y demás, como es el caso, por ejemplo, del API de java, esto es
inviable. Para poder omitir esta ruta, se utiliza la sentencia import que irá justo después de la sentencia package y antes de la definición de la clase. Podemos utilizar
tantas sentencias import como necesitemos (una debajo de otra). Además, podemos utilizar el comodín * para indicar que queremos importar todas las clases de un
paquete. Con el uso del * importamos todas las clases de ese paquete, pero no las clases que hayan en sus subpaquetes, para ello deberíamos utilizar diferentes
sentencias import. El uso del * no tiene ningún inconveniente y no hace que nuestra clase vaya a ocupar más o se ejecute más lenta, ni nada de eso. Es más, el uso
del * está recomendado.
import org.iesalandalus.programacion.utilidades.*;
...
int i = Entrada.entero();

Pero para clases que contienen miembros estáticos, podemos utilizar la sentencia import static seguido por el nombre de los miembros que queremos importar o
usando el * para importarlos todos y así evitar anteponer al miembro el nombre de la clase.

import static org.iesalandalus.programacion.utilidades.Entrada.*;


import static java.lang.Math.*;
...
double radio = entero();
double perimetro = 2 * PI * radio;

Enumerados
Los enumerados son clases especiales cuyos objetos se definen en la definición del enumerado, es decir, son conocidos en tiempo de compilación. Para declararlos
utilizamos la palabra reservada enum seguida del identifcador del enumerado y entre las llaves encerramos los objetos, separados por comas. En su forma más
sencilla sería almacenar constantes relacionadas.

public enum Direccion {


ARRIBA,
ABAJO,
DERECHA,
IZQUIERDA;
}

Su implementación utilizando una clase, podría ser como sigue (los enumerados existen a partir de la versión 5):
public class Direccion {
public static final Direccion ARRIBA = new Direccion();
public static final Direccion ABAJO = new Direccion();
public static final Direccion DERECHA = new Direccion();
public static final Direccion IZQUIERDA = new Direccion();

private Direccion() {}
}

Para cada valor del enumerado se le asigna una constante, llamada ordinal empezando en 0. También contiene métodos como: values() que devuelve todos los
valores del enumerado, valueOf(String) que nos devuelve el valor del enumerado representado por dicha cadena y toString que nos devuelve la representación del
valor. Los enumerados tamibén se pueden utilizar en sentencias switch.

Por ejemplo, el siguiente código recorrería los valores del enumerado y mostraría su ordinal y su representación como cadena. El bucle que hemos utilizado para
recorrer los valores del ordinal es un bucle for-each que se utiliza para recorrer colecciones y en el que en cada iteración la variable toma un valor, pero que veremos
más adelante cuando hablemos de colecciones.
...
for (Direccion direccion : Direccion.values()) {
System.out.println("Ordinal: " + direccion.ordinal() + ", nombre: " + direccion);
}
...

Su salida sería la que se muestra a continuación:

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 15/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
Ordinal: 0, nombre: ARRIBA
Ordinal: 1, nombre: ABAJO
Ordinal: 2, nombre: DERECHA
Ordinal: 3, nombre: IZQUIERDA

Los enumerados también pueden tener atributos, constructores que deben ser privados, métodos, etc.

Veamos un ejemplo de uso con su constructor y unos atributos.

public enum Mes {


ENERO("Enero", 31),
FEBRERO("Febrero", 28),
MARZO("Marzo", 31),
ABRIL("Abril", 30),
MAYO("Mayo", 31),
JUNIO("Junio", 30),
JULIO("Julio", 31),
AGOSTO("Agosto", 31),
SEPTIEMBRE("Septiembre", 30),
OCTUBRE("Octubre", 31),
NOVIEMBRE("Noviembre", 30),
DICIEMBRE("Diciembre", 31);

private String cadenaAMostrar;


private int numeroDias;

private Mes(String cadenaAMostrar, int numeroDias) {


this.cadenaAMostrar = cadenaAMostrar;
this.numeroDias = numeroDias;
}

public int getNumeroDias() {


return numeroDias;
}

@Override
public String toString() {
return cadenaAMostrar;
}
}
...
for (Mes mes : Mes.values()) {
System.out.println(mes + " tiene " + mes.getNumeroDias() + " días.");
}

Daría como salida:

Enero tiene 31 días.


Febrero tiene 28 días.
Marzo tiene 31 días.
Abril tiene 30 días.
Mayo tiene 31 días.
Junio tiene 30 días.
Julio tiene 31 días.
Agosto tiene 31 días.
Septiembre tiene 30 días.
Octubre tiene 31 días.
Noviembre tiene 30 días.
Diciembre tiene 31 días.

Por último nos dicen que los personajes sólo aceptarán tres colores: rojo, verde y azul (queremos que su representación como cadena sea su nombre con
la primera letra en mayúsculas y lo demás en minúsculas). Además nos comentan que quieren que nuestro personaje pueda moverse en una determinada
dirección: arriba, abajo, derecha e izquierda, y un determinado número de pasos (además de como se podía mover anteriormente). También nos
comentan que quieren que metamos todo en un paquete llamado videojuego. Pues el resultado final de nuestro diagrama de clases y el código final de
todas nuestras clases sería el siguiente (debemos tener en cuenta que el diagrama de clases no me permite mostrar sus atributos o métodos, por lo que
para el enumerado Color lo anotado junto a su representación para que quede claro):

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 16/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

Direccion.java

package videojuego;

public enum Direccion {


ARRIBA,
ABAJO,
DERECHA,
IZQUIERDA;
}

Color.java

package videojuego;

public enum Color {


ROJO("Rojo"),
VERDE("Verde"),
AZUL("Azul");

private String cadenaAMostrar;

private Color(String cadenaAMostrar) {


this.cadenaAMostrar = cadenaAMostrar;
}

@Override
public String toString() {
return cadenaAMostrar;
}
}

Posicion.java

package videojuego;

import java.util.Objects;

public class Posicion {

private static final int MIN_X = 0;


private static final int MAX_X = 100;
private static final int MIN_Y = 0;
private static final int MAX_Y = 100;

private int x;
private int y;

public Posicion() {
x = MIN_X;
y = MIN_Y;
}

public Posicion(int x, int y) {


setX(x);
setY(y);
}

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 17/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

public Posicion(Posicion posicion) {


if (posicion == null) {
throw new NullPointerException("No puedo copiar una posición nula.");
}
setX(posicion.getX());
setY(posicion.getY());
}

public int getX() {


return x;
}

public void setX(int x) {


if (x < MIN_X) {
throw new IllegalArgumentException("El valor de la x es menor que el mínimo permitido.");
} else if (x > MAX_X) {
throw new IllegalArgumentException("El valor de la x es mayor que el máximo permitido.");
}
this.x = x;
}

public int getY() {


return y;
}

public void setY(int y) {


if (y < MIN_Y) {
throw new IllegalArgumentException("El valor de la y es menor que el mínimo permitido.");
} else if (y > MAX_Y) {
throw new IllegalArgumentException("El valor de la y es mayor que el máximo permitido.");
}
this.y = y;
}

@Override
public int hashCode() {
return Objects.hash(x, y);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Posicion other = (Posicion) obj;
return x == other.x && y == other.y;
}

@Override
public String toString() {
return String.format("x=%s, y=%s", x, y);
}

Personaje.java

package videojuego;

import java.util.Objects;

import javax.naming.OperationNotSupportedException;

public class Personaje {

private static final String PREFIJO_NOMBRE = "Personaje ";


private static final int ENERGIA_INICIAL = 100;
private static final Color COLOR_INICIAL = Color.ROJO;
private static final int MIN_ENERGIA = 0;
private static final int MAX_ENERGIA = 100;

private static int numPersonajes = 0;

private String nombre;


private int energia;
private Color color;
private Posicion posicion;

public Personaje() {
numPersonajes++;
nombre = PREFIJO_NOMBRE + numPersonajes;
energia = ENERGIA_INICIAL;
color = COLOR_INICIAL;
posicion = new Posicion();
}

public Personaje(String nombre) {


this();
setNombre(nombre);
}

public Personaje(String nombre, int energia, Color color, Posicion posicion) {


this();
setNombre(nombre);
setEnergia(energia);
setColor(color);
setPosicion(posicion);
}

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 18/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
public Personaje(Personaje personaje) {
this();
if (personaje == null) {
throw new NullPointerException("No puedo copiar un personaje nulo.");
}
nombre = personaje.getNombre();
energia = personaje.getEnergia();
color = personaje.getColor();
posicion = new Posicion(personaje.posicion);
}

public String getNombre() {


return nombre;
}

private void setNombre(String nombre) {


if (nombre == null) {
throw new NullPointerException("El nombre del personaje no puede ser nulo.");
}
this.nombre = nombre;
}

public int getEnergia() {


return energia;
}

private void setEnergia(int energia) {


if (energia < MIN_ENERGIA) {
throw new IllegalArgumentException("El valor de la energia no puede ser menor que el mínimo establecido.");
} else if (energia > MAX_ENERGIA) {
throw new IllegalArgumentException("El valor de la energia no puede ser mayor que el máximo establecido.");
}
this.energia = energia;
}

public Color getColor() {


return color;
}

public void setColor(Color color) {


if (color == null) {
throw new NullPointerException("El color del personaje no puede ser nulo.");
}
this.color = color;
}

public Posicion getPosicion() {


return posicion;
}

private void setPosicion(Posicion posicion) {


if (posicion == null) {
throw new NullPointerException("La posición del personaje no puede ser nula.");
}
this.posicion = posicion;
}

public void chocar(int posiblePerdida) {


setEnergia(energia - posiblePerdida);
}

public void charlar(int posibleGanancia) {


setEnergia(energia + posibleGanancia);
}

public void mover(int x, int y) throws OperationNotSupportedException{


try {
posicion.setX(posicion.getX() + x);
posicion.setY(posicion.getY() + y);
} catch (IllegalArgumentException e) {
throw new OperationNotSupportedException("Movimiento no válido: " + e.getMessage());
}
}

public void mover(Direccion direccion, int pasos) throws OperationNotSupportedException {


if (direccion == null) {
throw new IllegalArgumentException("La dirección no puede ser nula.");
}
if (pasos <= 0) {
throw new IllegalArgumentException("El número de pasos debe ser mayor que cero.");
}
String movimientoNoValido = "Movimiento no válido: ";
switch (direccion) {
case ARRIBA:
try {
posicion.setX(posicion.getY() + pasos);
} catch (IllegalArgumentException e) {
throw new OperationNotSupportedException(movimientoNoValido + e.getMessage());
}
break;
case ABAJO:
try {
posicion.setX(posicion.getY() - pasos);
} catch (IllegalArgumentException e) {
throw new OperationNotSupportedException(movimientoNoValido + e.getMessage());
}
break;
case DERECHA:
try {
posicion.setX(posicion.getX() + pasos);
} catch (IllegalArgumentException e) {
throw new OperationNotSupportedException(movimientoNoValido + e.getMessage());
}
break;

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 19/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/
case IZQUIERDA:
try {
posicion.setX(posicion.getX() - pasos);
} catch (IllegalArgumentException e) {
throw new OperationNotSupportedException(movimientoNoValido + e.getMessage());
}
break;
default:
break;
}
}

@Override
public int hashCode() {
return Objects.hash(color, energia, nombre, posicion);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Personaje other = (Personaje) obj;
return Objects.equals(color, other.color) && energia == other.energia && Objects.equals(nombre, other.nombre)
&& Objects.equals(posicion, other.posicion);
}

@Override
public String toString() {
return String.format("nombre=%s, energia=%s, color=%s, posicion=(%s)", nombre, energia, color, posicion);
}

Ejercicios
Persona

Debes implementar una clase Personacomo muestra el diagrma de clases. Por simplificar considera todos los atributos como String. Controla las excepciones
cuando se pasan valores nulos. Los métodos setXson públicos por motivos didácticos que veremos en el próximo ejercicio. Cuando la hayas implementado
debes reflexionar sobre si crees que es un buen diseño o no (te puede servir de ayuda la advertencia que te muestra el plugins Sonarlint). Crea también una
clase en la que puedas probar su creación con los diferentes constructores y su uso.

Persona refactorizado

Después de analizar los problemas del diseño anteriorte sugiero que implementes la clase Persona de la siguiente manera. Al igual que en el ejemplo anterior
he dejado todos los métodos setX públicos para hacer que las clase sean mutables (aunque en la realidad eso no sería lo más adecuado) y te propongo que en la
implementación evites el problema del aliasing que pudiese surgir. Crea también una clase en la que puedas probar su creación con los diferentes constructores
y su uso, y analiza las diferencias con la solución anterior.

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 20/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/introduccionPOO/

Programación mantenido por IES-Al-Andalus

Ver en GitHub

https://ies-al-andalus.github.io/Programacion/introduccionPOO/ 21/21
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/

Programación
Repositorio sobre programación en java del IES Al-Ándalus
Algunas clases interesantes en Java
Arrays
Cadenas de caracteres
Expresiones regulares
Fechas y tiempos
Generación de números aleatorios en java
Ejercicios

Algunas clases interesantes en Java


En este apartado encontrarás una breve descripción sobre algunas clases interesantes en Java y cuya utilización te será de gran ayuda. Es esencial que conozcas estas
clases y que domines su utilización.

Al igual que en otros apartados, el objetivo principal es que tengáis una amplía gama de ejercicios, con sus respectivas posibles soluciones.

José Ramón Jiménez Reyes

@JRJimenezReyes

joseramon.jimenez@iesalandalus.org

Contenidos

Arrays
Cadenas de caracteres
Expresiones regulares
Fechas y tiempos
Generación de números aleatorios en java
Ejercicios

Arrays
Un array en java no es más que una estructura de memoria capaz de almacenar objetos (llamados elementos cuando hablamos de arrays) del mismo tipo. Un array en
sí es un objeto. A los elementos almacenados en un array se puede acceder mediante un índice que indica el desplazamiento desde el comienzo de dicho array. A la
hora de crear un array se establece la longitud del mismo y ésta no se puede cambiar.

Como he comentado, un array es un objeto en java. Por tanto, primero deberemos declarar una referencia a dicho array, después crear el array y posteriormente
inicializar su contenido. Veamos cómo se lleva a cabo cada una de estas operaciones.

Para declarar una referencia a un array utilizaremos la siguiente nomenclatura: tipo[] nombre, donde tipo puede ser un tipo primitivo, una clase de la API de java o
una clase definida por nosotros mismos. Los corchetes indican que estamos declarando un array. Y nombre será el identificador que le damos a nuestra referencia al
array.

int[] miArray;

Con esto habremos declarado la referencia al array identificada por miArray. Pero, al igual que ocurría con los objetos, ahora mismo esa referencia es null. Por lo
que deberemos crear el array y asignarlo a dicho identificador, para que la referencia apunte al array recién creado. Para crear el array se utiliza el operador new
seguido por el tipo de objetos que va almacenar el array y entre corchetes la longitud que queremos asignar a dicho array. La longitud puede ser un valor constante o
un valor calculado en tiempo de ejecución.

miArray = new int[15];

Ahora que ya tenemos creado el array deberemos inicializar su contenido. En un array recién creado, todos sus elementos tendrán los valores por defecto del tipo
que almacena (en el ejemplo estará todo a 0 que es el valor por defecto para los enteros).

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 1/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Para poder darle los valores deseados a cada uno de los elementos del array, deberemos acceder a cada elemento en particular y asignarle un valor. Para ello
utilizaremos el identificador del array y entre corchetes el índice del elemento al que queremos acceder. Notar que el primer índice de un array es el 0.
miArray[0] = 2;
miArray[1] = 4;
miArray[2] = 6;
System.out.println(miArray[2]); //Muestra 6
System.out.println(miArray[3]); //Muestra 0 ya que aún no le hemos asignado ningún valor

Existe una sintaxis abreviada que nos permite declarar, crear e inicializar un array en una sola sentencia:
int[] miArray = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 };

En este caso la longitud del array se determina por el número de elementos específicados entre las llaves.

Los arrays, al tratarse de objetos, tienen algunos miembros que podemos utilizar. Uno de los más utilizados es el atributo length que nos devuelve la longitud de
dicho array.

Para recorrer un array podemos utilizar un bucle. Es muy importante que controlemos que no nos salimos de los límites del array, tanto por abajo como por arriba (0
… length - 1), o de lo contrario nos saltará una excepción en tiempo de ejecución indicando que nos hemos salido de los límites del array
(IndexOutOfBoundsException).

...
for (int i = 0; i < miArray.length; i++) {
System.out.println("Elemento en el índice: " + i + ", valor: " + miArray[i]);
}

Pero java define otro tipo de bucles, que no habíamos comentado, que permite recorrer colecciones de objetos, entre ellas arrays, de una forma más cómoda y
sencilla. Este tipo de bucles son los llamados foreach. La estructura de este array es la siguiente: for (tipoBase identificador: coleccionTipoBase). tipoBase sería
el tipo que almacena la colección, en este caso el array, identificador sería un identificador de dicho tipo y coleccionTipoBase sería la colección, en este caso array,
a recorrer. Este tipo de bucle recorre la colección, en este caso el array, desde el principio al final y en cada iteración asigna el valor del elemento de la colección a la
variable identificador.
int indice = 0;
for (int elemento : miArray) {
System.out.println("Elemento en el índice: " + indice++ + ", valor: " + elemento);
}

Arrays multidimensionales

Los arrays multidimensionales son arrays de varias dimensiones, aunque pasar de tres dimensiones es complicado ya que su uso no es nada intuitivo.

Lo más normal es la utilización de arrays de dos dimensiones, también llamados matrices. Son arrays de arrays. Para declararlos utilizaremos tantos corchetes como
dimensiones queramos declarar.
char[][] crucigrama;

Al igual que ocurría con los arrays unidimensionales de objetos, una vez declarada la referencia hay que crear los diferentes elementos del array que en este caso
también son arrays. Esto podemos hacerlo directamente con el operador new.
crucigrama = new char[5][5];

La forma de representación anterior no sería la más correcta, ya que en verdad, en java un array multidimensional es un array de arrays, por lo que la representación
correcta sería la siguiente (aunque por simplicidad utilizaremos la anterior).

También es posible que un array multidimensional tuviese diferentes dimensiones en cada una de las posiciones;

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 2/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
int[][] escalon;
escalon = new char[5][];
for (int i = 0; i < escalon.length; i++) {
escalon[i] = new int[i+1];
}

Esto simplemente os lo comento para que lo sepáis, aunque no es muy normal utilizarlo.

Para acceder a los elementos de un array multidimensional deberemos utilizar tantos índices como dimensiones tenga el array.
crucigrama[0][0] = 'a';
crucigrama[0][1] = 'n';
...
crucigrama[1][0] = 'b';
...
crucigrama[4][4] = 'l';

Veamos cómo recorrer el array multidimensional primero para asignarle valores a sus elementos y luego para mostrarlos.
...
char[][] crucigrama;
crucigrama = new char[5][5];
char letra = 'a';
for (int i = 0; i < crucigrama.length; i++) {
for (int j = 0; j < crucigrama[i].length; j++) {
crucigrama[i][j] = letra++;
}
}
for (int i = 0; i < crucigrama.length; i++) {
for (int j = 0; j < crucigrama[i].length; j++) {
System.out.println("Elemento [" + i + ", " + j + "] = " + crucigrama[i][j]);
}
}

Arrays de objetos

Hasta ahora hemos visto todos los ejemplos con arrays de tipos primitivos. Pero también comenté que los arrays podían almacenar cualquier objeto.

Tanto la declaración, como la creación se hacen exactamente igual, aunque en vez de utilizar como tipo un tipo primitivo, se utiliza la clase del objeto que queramos
almacenar.

Lo que sí debemos tener en cuenta que una vez creado el array todos sus elementos están inicializados a los valores por defecto, que en el caso de referencias a
objetos es null. Por lo que debemos crear cada objeto y asignarlo a ese elemento antes de poder acceder al mismo.
package arrays;

class Punto {
private int x;
private int y;

public Punto(int x, int y) {


this.x = x;
this.y = y;
}

public Punto(Punto otro) {


x = otro.x;
y = otro.y;
}

public int getX() {


return x;
}

public void setX(int x) {


this.x = x;
}

public int getY() {


return y;
}

public void setY(int y) {


this.y = y;
}

@Override
public String toString() {
return "Punto [x=" + x + ", y=" + y + "]";
}

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 3/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/

public class ArrayObjetos {

public static void main(String[] args) {


Punto[] cuadrado = new Punto[4];
cuadrado[0] = new Punto(0, 0);
cuadrado[1] = new Punto(10, 0);
cuadrado[2] = new Punto(10, 10);
cuadrado[3] = new Punto(0, 10);

for (Punto punto: cuadrado) {


System.out.println(punto);
}

System.out.println("El primer vértice del cuadrado, tiene la x=" +


cuadrado[0].getX() + " y la y=" + cuadrado[0].getY());
}
}

Copiando arrays

A la hora de copiar un array debemos tener cuidado con lo que de verdad pretendemos. Aunque voy a hablar de copiar, el caso sería el mismo para devolverlo por un
método o asignarlo a un atributo.

La primera idea sería asignar un array a otro, pero como ya sabemos lo que en verdad sucede es que se están copiando las referencias, por lo que cualquier
modificación que hagamos en cualquiera de los arrays afectaría al otro.
...
Punto[] arrayOrigen = { new Punto(1, 1) };
Punto[] arrayDestino = new Punto[1];
arrayDestino = arrayOrigen;
arrayOrigen[0].setX(2);

System.out.println(arrayOrigen == arrayDestino); //Muestra true


System.out.println(arrayOrigen[0] + ", "
+ arrayDestino[0]); //Muestra Punto[x=2, x=1], Punto[x=2, y=1]

Otra opción sería utilizar el método clone que se hereda de Object, pero esta opción tampoco es una solución adecuada, ya que utilizando este método se crea una
nueva referencia del array pero para cada elemento se igualan las referencias.
...
Punto[] arrayOrigen = { new Punto(1, 1) };
Punto[] arrayDestino = new Punto[1];
arrayDestino = arrayOrigen.clone();
arrayOrigen[0].setX(2);

System.out.println(arrayOrigen == arrayDestino); //Muestra false


System.out.println(arrayOrigen[0] + ", "
+ arrayDestino[0]); //Muestra Punto[x=2, x=1], Punto[x=2, y=1]

Hay otros métodos que también permiten hacer una copia de un array como el método arraycopy de la clase System la cuál acepta como parámetros el array de
origen, la posición de origen, el array de destino, la posición del destino en qué copiarlo y la longitud a copiar. Pero este método también se comporta como en el
caso anterior.
...
Punto[] arrayOrigen = { new Punto(1, 1) };
Punto[] arrayDestino = new Punto[1];
System.arraycopy(arrayOrigen, 0, arrayDestino, 0, 1);
arrayOrigen[0].setX(2);

System.out.println(arrayOrigen == arrayDestino); //Muestra false


System.out.println(arrayOrigen[0] + ", "
+ arrayDestino[0]); //Muestra Punto[x=2, x=1], Punto[x=2, y=1]

Por último, existen dos métodos de la clase java.util.Arrays cuyos identificadores son: copyOf que acepta como parámetros el array de origen y la longitud a copiar
y devuelve la copia de dicho fragmento del array copiado; y copyOfRange que acepta como parámetros el array origen, el índice desde el que va a copiar y el índice
donde termina de copiar (sin incluirlo) y devuelve el fragmento de array copiado. Pero ambos métodos se comportan exactamente igual que en el caso anterior.

Podemos comprobar que las técnicas utilizadas para realizar una copia de un array, simplemente realizan una copia superficial del array (exceptuando el operador =
que simplemente iguala las referencias).

Si lo que pretendemos es realizar una copia profunda del array, lo que deberemos hacer es: crear un constructor copia de la clase que almacena el array (y de sus
clases cliente, es decir, que realice una copia profunda de dicha clase) y recorrer el array creando nuevas instancias de cada uno de los elementos.
...
Punto[] arrayOrigen = { new Punto(1, 1) };
Punto[] arrayDestino = new Punto[1];
for (int i = 0; i < arrayOrigen.length; i++) {
arrayDestino[i] = new Punto(arrayOrigen[i]);
}
arrayOrigen[0].setX(2);

System.out.println(arrayOrigen == arrayDestino); //Muestra false


System.out.println(arrayOrigen[0] + ", "
+ arrayDestino[0]); //Muestra Punto[x=2, x=1], Punto[x=1, y=1]

Algunos otros métodos interesantes

La clase java.util.Arrays nos ofrece algunos otros métodos (además de los vistos) que nos pueden servir de ayuda en algún momento. Veamos algunos de los
métodos más interesantes:

fill: acepta como parámetros el array y el valor a asignar a cada elemento y rellena cada elemento del array con dicho valor. Hay que tener cuidado ya que
simplemente hace una asignación del valor, por lo que si es una referencia tendremos el problema del aliasing para cada elemento.
sort: ordena el array en orden ascendente. Este método trabaja correctamente con tipos primitivos, pero si queremos ordenar un array de objetos, la clase de
dicho objeto debe implementar la interfaz Comparable o que le pasemos el método para comparar (utilizando una expresión lambda, por ejemplo), pero esto lo

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 4/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
dejamos para más adelante.
binarySearch: utiliza el algoritmo de búsqueda binaria para buscar un elemento en el array y devuelve la posición + 1 (en negativo) de dicho elemento. El
requisito para que este método funcione correctamente es que el array esté previamente ordenado. Al igual que en el caso anterior, este método funciona
correctamente con tipos primitivos pero si hablamos de objetos, la clase debe implementar la interfaz Comparator o que le pasemos el método para comparar.

Cadenas de caracteres
La clase String representa una cadena de caracteres. Internamente podríamos verla como un array de caracteres.

Dado que el uso de cadenas de caracteres es muy común en los lenguajes de programación, en java la clase String está a medio camino entre un tipo primitivo y una
clase. Debido a su amplio uso, su declaración e inicialización se parecen más a un tipo primitivo que a un objeto de una clase. Además, es importante mencionar que
es un objeto inmutable, es decir, que una vez creado no puede cambiar su contenido.

Para crear una cadena, podemos utilizar dos formas:

Implícita: Asignamos la referencia al objeto String a un literal del tipo cadena que se delimita por "". String nombre = "José Ramón";.
Explícita: Se crea como cualquier otra referencia a un objeto de una clase. String nombre = new String("José Ramón");.

La diferencia entre crear una cadena de una forma u otra es, en principio la sencillez y comodidad de utilizar la forma implícita. Pero, además, existe otra diferencia:
por cuestiones de eficiencia, cuando creamos una cadena utilizando la forma implícita, java almacenará los literales en una zona especial e intentará reutilizar todos
los literales iguales. Sin embargo, cuando utilizamos la forma explícita se comportará como con cualquier otro objeto y los almacenará en el heap de memoria.
String nombre1 = "José Ramón";
String nombre2 = "José Ramón";
String nombre3 = new String("José Ramón");
String nombre4 = new String("José Ramón");
System.out.println(nombre1 == nombre2); //Muestra true
System.out.println(nombre3 == nombre4); //Muestra false

Como hemos comentado anteriormente, las cadenas son objetos inmutables, por lo que cualquier operación que hagamos sobre ella, que implique una modificación,
lo que hará es crear una nueva cadena, ya que los objetos inmutables no se pueden modificar.

Debido al amplio uso de las cadenas, también existe un operador especial para concatenar cadenas: +.

String nombre = "José Ramón";


System.out.println("Hola " + nombre); //Muestra: Hola José Ramón

Vistas las peculiaridades de las cadenas, veamos los principales métodos que nos ofrece la clase String para su manipulación y uso.

Método Explicación
Devuelve el carácter especificado por el índice. El índice debe estar entre 0 y length() - 1 o de lo contrario lanzará la
char charAt(int index)
excepción IndexOutOfBoundsException.
String concat(String str) Devuelve la concatenación de la cadena actual con la pasada por parámetro.
boolean contains(CharSequence s) Devuelve true si la cadena contiene la secuencia de caracteres pasada por parámetro.
Compara la cadena actual con la pasada por parámetro y devuelve -1, 0 o 1, dependiendo de si la pasada es menor, igual o
int compareTo()
mayor.
int compareToIgnoreCase() Igual que el anterior, pero ignorando mayúsculas y minúsculas.
boolean endsWith(String suffix) Devuelve true si la cadena actual termina con el sufijo suffix.
boolean equals(Object obj) Compara si el contenido de la cadena actual y la pasada por paŕametro es el mismo.
boolean equalsIgnoreCase(String
Compara si el contenido de la cadena actual y la pasada por paŕametro es el mismo, ignorando mayúsculas y minúsculas.
string)
int indexOf(char ch) Devuelve el índice de la primera ocurrencia del carácter ch o -1 si no existe.
int indexOf(char ch, int
Igual que la anterior, pero comienza la búsqueda a partir del índice fromIndex.
fromIndex)
int indexOf(String str) Devuelve el índice de la primera ocurrencia de la subcadena str.
boolean isEmpty() Devuelve true si la longitud de la cadena es igual a 0.
Devuelve el índice que ocupa la última ocurrencia del caracter. Este método está sobrecargado para que también se le pueda
int lastIndexOf(char ch)
pasar una cadena en vez de un caracter.
int lastIndexOf(char ch, int Lo mismo que la anterior, pero comienza la búsqueda desde el índice fromIndex hacia atrás. Al igual que el anterior, este
fromIndex) método también está sobrecargado para que se le pueda pasar una cadena en vez de un caracter.
int length() Devuelve la longitud de la cadena.
https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 5/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Método Explicación
boolean startsWith(String prefix) Devuelve true si la cadena actual comienza por la cadena prefix.
boolean startsWith(String prefix,
Devuelve true si la cadena actual empieza por la cadena prefix empezando en el índice offset.
inf offset)
String substring(int beginIndex) Devuelve la subcadena de la cadena actual, empezando en beginIndex hasta el final.
String substring(int beginIndex,
Devuelve la subcadena de la cadena actual, empezando en beginIndex hasta endIndex - 1.
int endIndex)
String toUpperCase() Devuelve la cadena en mayúsculas.
String toLowerCase() Devuelve la cadena en minúsculas.
String trim() Devuelve la cadena eliminando los espacios en blanco iniciales y finales.

Para consultar la lista completa de métodos, os recomiendo consultar la documentación de la API de java.

Formateando cadenas

Hay veces que nos interesa dar formato a una cadena o incluso convertir un tipo a cadena pero con un determinado formato.

Para este menester podemos utilizar el método format de clase String. A este método se le pasa una cadena con el formato deseado y una lista variable de
argumentos. Esta lista variable de argumentos dependerá de la cadena de formato pasada como primer argumento, es decir, de los especificadores de formato
utilizados en la misma. De esta misma forma podemos formatear la salida por consola (o por el error) mediante el método printf del objeto out de la clase System.
Como luego podréis observar, uno de los principales beneficios es evitar el uso de múltiples concatenaciones a la hora de imprimir por consola (además de hacerlo
con el formato deseado).

Primero os voy a mostrar los tipos de conversión de formato más comunes y luego veremos algunos ejemplos de sus usos.

Tipo de conversión Tipo al que se aplica Salida


%a Números reales El número real expresado en hexadecimal
%b Cualquier tipo true si no es nulo y false en caso contrario
%c caracter El caracter
%d Enteros El número entero
%e Números reales El número real expresado en notación científica
%f Números reales El número real
%n Nada Un separador de línea dependiente de la plataforma
%o Enteros El número entero expresado en octal
%s Cualquier tipo Su representación como cadena
%x Enteros El número entero expresado en notación hexadecimal

Veamos un primer ejemplo de cómo utilizarlos y luego los explicaremos más detalladamente.

String nombre = "José Ramón";


int edad = 18;
String salida = String.format("Mi nombre es %s y tengo %d años.", nombre, edad);
System.out.println(salida);
//Salida: Mi nombre es José Ramón y tengo 18 años.
System.out.printf("Mi nombre es %s y tengo %d años.", nombre, edad);
//Salida: Mi nombre es José Ramón y tengo 18 años.

Un especificador de formato, además puede ir acompañado de otros modificadores de formato que son opcionales. Veamos cuál es la sintaxis:
%[indice_argumento$][modificador][anchura][.precision]tipo_conversion

Como se puede apreciar sólo es obligatorio el símbolo % acompañado del tipo de conversión. El índice de argumento se utiliza para referirnos a un parámetro en
cuestión. El modificador se utiliza para cambiar el formato de salida. La anchura se utiliza para limitar el número de caractares a mostrar. La precisión se utiliza para
limitar el número de caracteres a mostrar dependiendo del tipo de conversión.

En el siguiente ejemplo utilizo los índices para hacer lo mismo que en el primer ejemplo.

System.out.printf("Mi nombre es: %2$s y tengo %1$d años.", edad, nombre);

En el siguiente ejemplo vamos a ver cómo formatear números enteros.

int numero = 1234;


System.out.printf("|%d|%n", numero); //Mostramos el número sin más
System.out.printf("|%10d|%n", numero); //Mostramos el número especificando su longitud
System.out.printf("|%-10d|%n", numero); //Mostramos el número especificando su longitud y justificado a la izquierda
System.out.printf("|%010d|%n", numero); //Mostramos el número especificando su longitud y rellenamos de 0`s el tamaño no ocupado
System.out.printf("|%,10d|%n", numero); //Mostramos el número especificando su longitud y utilizando de separador de miles el separador local

La salida será la siguiente:


|1234|
| 1234|
|1234 |
|0000001234|
| 1.234|

En el siguiente ejemplo vamos a ver cómo formatear números reales


double q = 1.0 / 3.0;
System.out.printf("1.0/3.0 = %5.3f %n", q);
System.out.printf("1.0/3.0 = %7.5f %n", q);
q = 1.0 / 2.0;
System.out.printf("1.0/2.0 = %09.3f %n", q);

Su salida sería:

1.0/3.0 = 0,333
1.0/3.0 = 0,33333
1.0/2.0 = 00000,500

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 6/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
En el siguiente ejemplo vamos a ver cómo formatear cadenas

String nombre = "José Ramón";


System.out.printf("Nombre: |%s|%n", nombre);
System.out.printf("Nombre: |%15s|%n", nombre); //Especifico anchura
System.out.printf("Nombre: |%-15s|%n", nombre); //Especifico anchura y justificación izquierda
System.out.printf("Nombre: |%.5s|%n", nombre); //Especifico máximo de caracteres a mostrar
System.out.printf("Nombre: |%15.5s|%n", nombre); //Especifico anchura y número máximo de caracteres a mostrar

Su salida sería:
Nombre: |José Ramón|
Nombre: | José Ramón|
Nombre: |José Ramón |
Nombre: |José |
Nombre: | José |

Cadenas de caracteres mutables

Como ya mencioné, un objeto de la clase String es un objeto inmutable, lo que quiere decir que no puede cambiar una vez creados. Cualquier operación sobre dicho
objeto que conlleve una modificación del mismo lo que hace es crear un nuevo objeto. Por ejemplo, el siguiente código:
String nombre = "José";
nombre += " ";
nombre += "Ramón";

Este código crea tres objetos. El primero en la inicialización y uno por cada concatenación. Cuando trabajamos con pocas de estas operaciones no importa, pero si
realizamos muchas de ellas si puede penalizar en recursos y/o tiempo.

Para solventar este problema java nos ofrece dos clases StringBuilder y StringBuffer. Su interfaz es exactamente la misma (los métodos que nos ofrecen) y con la
única diferencia que la primera clase no está sincronizada y la segunda sí. Por tanto, lo recomendable sería utilizar StringBuilder en entornos con un solo hilo y
StringBuffer en entornos multi-hilo.

Los objetos de dichas clases son estructuras dinámicas que pueden crecer cuando lo necesiten. Además de su longitud, tienen un atributo que indica su capacidad
(que siempre será mayor o igual que la longitud). Dicha capacidad puede crecer se así es necesario.

Un objeto String puede convertirse fácilmente en un objeto de una de las clases anteriores simplemente pasando el objeto String a la hora de crear el objeto. A la
inversa, simplemente debemos utilizar el método toString.

String nombre = "José Ramón";


StringBuilder nombreSB = new StringBuilder(nombre);
String otroNombre = nombreSB.toString();

Los principales métodos que nos ofrecen estas clases son:

Método Explicación
append(…) Añade al final la cadena o la representación en forma de cadena del objeto primitivo o cadena pasados
delete(int inicio, int final) Borra los caracteres comprendidos entre inicio y final - 1
deleteCharAt(int indice) Borra el caracter que ocupa el índice indice
insert(int indice, …) Inserta en la posición indice la cadena o la representación en forma de cadena del objeto primitivo o cadena pasados
replace(int inicio, int final, String str) Reemplaza los caracteres comprendidos entre inicio y final - 1 por la cadena pasada
setCharAt(int indice, char c) Reemplaza el caracter que ocupa el índice indice por el caracter c
reverse Invierte el contenido

Por lo dicho hasta ahora, utilizaremos String cuando no vayamos a realizar muchas operaciones con las mismas o estas no vayan a cambiar y en caso contrario
StringBuilder o StringBuffer.

Para mostrar la diferencia en rendimiento veamos la salida del siguiente programa:

final int TAMANO = 100000;


long inicio, tiempoConsumido;

String cadena = "";


char ch = 'a';
inicio = System.nanoTime();
for (int n = 0; n < TAMANO; ++n) {
cadena += ch;
++ch;
if (ch > 'z') {
ch = 'a';
}
}
tiempoConsumido = System.nanoTime() - inicio;

System.out.printf("Tiempo: %d s. en la construcción de un String de tamaño %d caracteres%n", tiempoConsumido / 1000, TAMANO);

StringBuilder cadenaSB = new StringBuilder();


ch = 'a';
inicio = System.nanoTime();
for (int n = 0; n < TAMANO; ++n) {
cadenaSB.append(ch);
++ch;
if (ch > 'z') {
ch = 'a';
}
}
tiempoConsumido = System.nanoTime() - inicio;

System.out.printf("Tiempo: %d s. en la construcción de un StringBuilder de tamaño %d caracteres%n", tiempoConsumido / 1000, TAMANO);

StringBuffer cadenaSBf = new StringBuffer();


ch = 'a';
inicio = System.nanoTime();
for (int n = 0; n < TAMANO; ++n) {
cadenaSBf.append(ch);

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 7/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
++ch;
if (ch > 'z') {
ch = 'a';
}
}
tiempoConsumido = System.nanoTime() - inicio;

System.out.printf("Tiempo: %d s. en la construcción de un StringBuffer de tamaño %d caracteres%n", tiempoConsumido / 1000, TAMANO);

El programa crea una cadena por concatenación de TAMANO caracteres y mide el tiempo empleado en ello y lo muestra. Hace lo mismo para un StringBuilder y un
StringBuffer.

La salida del mismo, en mi ordenador, es la siguiente, que creo que habla por ella misma (aunque los tiempos mostrados varían de una ejecución a otra, lo que no
varía es la magnitud de la diferencia).

Tiempo: 3549781 s. en la construcción de un String de tamaño 100000 caracteres


Tiempo: 1863 s. en la construcción de un StringBuilder de tamaño 100000 caracteres
Tiempo: 2817 s. en la construcción de un StringBuffer de tamaño 100000 caracteres

Expresiones regulares
Una expresión regular no es más que un patrón que empareja con una serie de cadenas. Una expresión regular puede utilizarse para contar cuántas veces se repite un
patrón en una cadena, comprobar si una cadena cumple con un determinado patrón, reemplazar un patrón en un texto por otra cadena, etc. La principal utilidad, bajo
mi punto de vista, será comprobar que los datos introducidos por un usuario cumplen con un determinado patrón y así evitar errores futuros.

Las expresiones regulares ya se venían utilizando en muchos otros lenguajes e incluso formaban parte de comandos del SO UNIX (tales como sed, awk). Es más, tú
sin saberlo las has utilizado cuando haces un listado desde la terminal de los ficheros que tengan tal extensión.

En este apartado veremos cómo definimos las expresiones regulares, es decir, cómo construimos el patrón para que empareje con las cadenas que queremos
reconocer. Una vez hecho esto, veremos cómo utilizarlas en java.

Construcción de expresiones regulares

Pasemos a ver cómo podemos construir expresiones regulares, pero sin profundizar mucho ya que de este tema se podría hacer un sólo libro como ya los hay:
Mastering Regular Expressions, 3nd Edition, Jeffrey E. F. Friedl, O’Reilly and Associates, 2006.

Caracteres

En las expresiones regulares podemos utilizar caracteres alfanuméricos que emparejan con ellos mismos y caracteres especiales que hay que escribirlos escapados.
Los más comunes son los siguientes:

Cararacter Con qué empareja


\\ Con el caracter \
\n Con un salto de línea
\r Con el retorno de carro
\t Con el tabulador

Además para construir expresiones regulares también se utilizan algunos caracteres que tienen un significado especial y que, por tanto, también debemos escapar.

En java el caracter de escape se representa por la doble barra \\ por lo que si queremos emparejar con el caracter \, en la expresión regular en java deberemos
escribir \\\\.

Clases de caracteres

Las clases de caracteres definen cuál será el contenido de un patrón. Veamos las principales:

Representación Significado
[abc] Coincide con sólo uno de los caracteres encerrados entre corchetes
[^abc] Negación. Coincide con sólo uno de los caracteres que no esté encerrado entre corchetes
[a-z] Coincide con sólo uno de los caracteres que representa el rango
[^a-z] Coincide con sólo uno de los caracteres que no esté representado por el rango
. Coincide con cualquier caracter excepto con el salto de línea
\w Coincide con cualquier caracter alfanumérico y el símbolo de subrayado. Equivale a [a-zA-Z0-9_]
\W Coincide con cualquier caracter no alfanumérico
\d Coincide con cualquier dígito. Equivale a [0-9]
\D Coincide con cualquier caracter que no sea un dígito
\s Coincide con cualquier caracter de espacio o salto de línea. Equivale a [\t\r\n\v\f]
\S Coincide con cualquier caracter que no sea un espacio o salto de línea

Operadores lógicos

Nos permiten combinar caracteres o clases de caracteres para formar nuevas expresiones regulares.

Operador Significado
AB Coincide con la expresión regular A seguida por la B
A|B Coincide con la expresión regular A o con la expresión regular B

Cuantificadores

Nos permiten indicar el número de veces que debe aparecer un elemento en una expresión regular.

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 8/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Cuantificador Significado
A{n} Coincide con la repetición de la expresión regular A n veces
A{n,} Coincide con la repetición de la expresión regular A n veces o más
A{n,l} Coincide con la repetición de la expresión regular A entre n y l veces
A? Coincide con la aparición de la expresión regular A 0 o 1 vez
A* Coincide con la aparición de la expresión regular A 0 o más veces (es decir, que A podría no aparecer)
A+ Coincide con la aparición de la expresión regular A al menos una vez

Agrupamientos

Podemos agrupar elementos para tratarlos como una sola unidad mediante su agrupamiento. Además, un grupo puede ser luego consultado. Para agrupar una serie de
elementos utilizamos los paréntesis (). Para referirnos a cada grupo lo haremos por su número de orden, sabiendo que se agrupa de grupos más externos a más
internos. Por ejemplo, la expresión ((A)(B)) contiene tres grupos. El primer grupos sería ((A)(B)), el segundo (A) y el tercero (B). También hay un grupo especial al
que nos referimos por el orden 0 que representa la expresión completa.

También podemos agrupar elementos pero sin capturarlos mediante el uso de los paréntesis y comenzando por ?:. Por ejemplo: (?:ABC).

Ejemplos

Es conveniente que practiques con las expresiones regulares ya que son muy utilizadas sobre todo para llevar a cabo validaciones. Para ello te recomiendo que
visites la siguiente página Expresiones regulares en la que puedes probar las expresiones regulares, consultar la documentación y además te explica qué hace cada
elemento, etc.

Veamos algunos ejemplos, aunque luego en los ejercicios veremos más.

Expresión regular que se ajuste a un código postal: \d{5}


Expresión regular que coincida con un número de teléfono: \d{9}
Expresión regular que coincida con números del 0 al 49: [0-4]?\d
Expresión regular que coincida con números hexadecimales: [\da-fA-F]+
Expresión regular que se ajuste a un número hexadecimal pero en el que no podamos mezclar mayúsculas y minúsculas (utilizamos unas u otras): (?:[\dA-
F]+)|(?:[\da-f]+)
Expresión regular que se ajuste a números de tres cifras del 000 al 255: (?:[0-1]\d{2})|(?:2[0-4]\d)|(?:25[0-5])

Expresiones regulares en java

Fechas y tiempos
En las versiones anteriores de java, trabajar con fechas y horas era muy tedioso y a veces se podía convertir en una locura. Pero en la versión 8 de java se han
esmerado en hacer que trabajar con fechas y horas sea sencillo. Lo primero que han hecho es unificar los nombres de los métodos, por lo que trabajar con fechas o
trabajar con horas es casi idéntico y, por tanto, su aprendizaje se hace mucho más sencillo. Todas las clases que veremos son inmutables (al igual que ocurría con la
clase String) y eso es importante que lo tengas en cuenta ya que, como sabes, esto quiere decir que dichos objetos no se pueden modificar.

En este apartado veremos cómo trabajar con ellas haciendo uso de los métodos más comunes y útiles, aunque para una completa explicación te recomiendo que le
eches un vistazo a la documentación de la API.

API LocalDate
API LocalTime
API LocalDateTime
API Instant
API Duration
API Period
API DateTimeFormatter
API ChronoField

Fechas

La clase para trabajar con fechas es LocalDate que pertenece al paquete java.time. Con esta clase podremos trabajar con fechas de forma sencilla. Veamos algunos
ejemplos.

El método que nos permite obtener la fecha actual es el método estático now().

LocalDate hoy = LocalDate.now();


System.out.println("La fecha de hoy es: " + hoy); //Imprime la fecha de hoy, aunque en formato inglés

Como hemos visto en el ejemplo anterior, la salida por consola no se adecua al formato que a lo mejor esperábamos. Para ello, podemos utilizar la clase
DateTimeFormatter. Esta clase tiene muchos patrones para elegir, tanto para fechas como para horas Para utilizarlos usaremos el método ofPattern de la clase
anterior para construir el patrón. Luego pasaremos el patrón al método format del objeto fecha. Veamos un ejemplo, partiendo del anterior.

DateTimeFormatter formatoCorto = DateTimeFormatter.ofPattern("d/M/yy");


DateTimeFormatter formatoLargo = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter formatoTextoLargo = DateTimeFormatter.ofPattern("cccc, d 'de' MMMM 'de' yyyy");
DateTimeFormatter formatoTextoCorto = DateTimeFormatter.ofPattern("ccc, d 'de' MMM 'de' yyyy");
System.out.println("La fecha de hoy en formato corto se expresa como: " + hoy.format(formatoCorto));
System.out.println("La fecha de hoy en formato largo se expresa como: " + hoy.format(formatoLargo));
System.out.println("La fecha de hoy expresada en texto corto es: " + hoy.format(formatoTextoCorto));
System.out.println("La fecha de hoy expresada en texto largo es: " + hoy.format(formatoTextoLargo));

También podemos crear una fecha concreta pasándole el día, mes y año mediante el método estático of.
LocalDate anoNuevo = LocalDate.of(2018, 1, 1);
System.out.println("Año nuevo: " + anoNuevo.format(formatoTextoLargo));

Nos ofrece métodos para consultar datos de la fecha, como conocer el año getYear, el mes getMonthValue (el método getMonth nos devuelve un enumerado
representado dicho mes), el día del año getDayOfYear, el día del mes getDayOfMonth, el día de la semana getDayOfWeek. Podemos consultar también si el año de una
fecha fue bisiesto isLeapYear o saber el número de días del mes lengthOfMonth.

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 9/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Dada una fecha también podemos manipularla: cambiar el año withYear, cambiar el mes withMonth, cambiar el día withDayOfMonth o cambiar lo que queramos
mediante el método with que como primer parámetro se le pasa un enumerado ChronoField que contiene una gran cantidad de constantes. Ten en cuenta que estos
métodos no modifican el objeto sobre que el actúan ya que estos objetos son inmutables y lo que hacen es devolver una nueva fecha con el resultado de dicha
manipulación.

Nos permite realizar operaciones con una fecha dada: añadir años plusYears, añadir meses plusMonths, añadir días plusDays o añadir lo que queramos mediante plus
y el uso de ChronoField. Lo mismo nos permite restar mediante los métodos minus*.

Los métodos equals y compareTo nos permiten comparar fechas. También podemos utilizar los métodos isBefore, isAfter y isEqual

Por último, podemos crear fechas a partir de cadenas (sería el proceso inverso al formateo) mediante el método parse.
String liberacionJava8 = "18/03/2014";
LocalDate fechaLiberacionJava8 = LocalDate.parse(liberacionJava8, formatoLargo);
System.out.println("Fecha de liberación de Java 8: " + fechaLiberacionJava8.format(formatoTextoLargo));

Tiempo

La clase para trabajar con tiempos en java 8 es la clase LocalTime. Esta clase representa la hora, minutos, segundos y nanosegundos de un tiempo dado. Su uso es
muy parecido al de las fechas.

Para obtener el tiempo actual se utiliza el método now.

LocalTime ahora = LocalTime.now();


System.out.println("Son las: " + ahora);

Como ocurría con las fechas, el formato de salida a lo mejor no es el esperado. Para personalizar el formato de salida utilizaremos la clase DateTimeFormatter con el
patrón adecuado. El proceso a seguir es el mismo que para una fecha.
DateTimeFormatter formatoCortoAMPM = DateTimeFormatter.ofPattern("h:m:s");
DateTimeFormatter formatoLargoAMPM = DateTimeFormatter.ofPattern("hh:mm:ss");
DateTimeFormatter formatoCorto24h = DateTimeFormatter.ofPattern("H:m:s");
DateTimeFormatter formatoLargo24h = DateTimeFormatter.ofPattern("HH:mm:ss");
DateTimeFormatter formatoTexto = DateTimeFormatter.ofPattern("h 'horas' m 'minutos' s 'segundos' a");
System.out.println("La hora actual en formato corto AM/PM se expresa como: " + ahora.format(formatoCortoAMPM));
System.out.println("La hora actual en formato largo AM/PM se expresa como: " + ahora.format(formatoLargoAMPM));
System.out.println("La hora actual en formato corto 24h se expresa como: " + ahora.format(formatoCorto24h));
System.out.println("La hora actual en formato largo 24h se expresa como: " + ahora.format(formatoLargo24h));
System.out.println("La hora actual expresada en texto es: " + ahora.format(formatoTexto));

También podemos crear un tiempo concreto pasándole la hora, los minutos, los segundos … mediante el método estático of.

LocalTime mediaNoche = LocalTime.of(0, 0, 0);


System.out.println("Media noche: " + mediaNoche.format(formatoLargoAMPM));

También posee métodos para consultar la hora getHour, los minutos getMinute, los segundos getMinute y los nanosegundos getNano.

Ofrece métodos para manipular un tiempo: cambiar la hora withHour, cambiar los minutos withMinute, cambiar los segundos withSecond, añadir o restar horas
plusHour y minusHour, añadir o restar minutos plusMinutes y minusMinutes, añadir o restar segundos plusSeconds y minusSecond y añadir o restar nanosegundos
plusNano y minusNano. Recordar que estos métodos no modifican el objeto fecha sobre el que actúa ya que dichos objetos son inmutables y lo que hacen es devolver
una copia con dicho objeto modificado.

Los métodos equals y compareTo nos permiten comparar tiempos. También se pueden utilizar los métodos isBefore, isAfter y isEquals.

También podemos crear tiempos a partir de cadenas (proceso inverso al formateo) mediante el método parse.

String mediaNocheString = "00:00:00";


mediaNoche = LocalTime.parse(mediaNocheString, formatoLargo24h);
System.out.println("Media noche desde cadena: " + mediaNoche.format(formatoTexto));

Fechas y tiempos agrupados

En java 8 también podemos representar en un solo objeto la fecha y el tiempo juntos mediante la clase LocalDateTime. Su uso es una combinación de las dos clases
vistas anteriormente. Por tanto no nos detendremos en más detalles.

Generación de números aleatorios en java


Math.random()

En la clase Math existe el método estático random que genera números aleatorios comprendidos en el siguiente intervalo: [0, 1), es decir entre cero (inclusive) hasta el
uno (exclusive).

Si nosotros queremos generar números enteros aleatorios comprendidos en un intervalo dado [x, y] ambos inclusive, debemos utilizar la siguiente sentencia, en la
que podremos realizar un casting a entero o bien utilizar el método Math.floor:

int x = 100;
int y = 200;
int aleatorio1 = (int) (Math.random() * (y - x + 1) + x);
int aleatorio2 = Math.floor(Math.random() * (y - x + 1) + x);

Clase Random

La clase Random pertenece al paquete java.util y nos ofrece más posibilidades y es la forma más recomendada (aunque si queremos podemos utilizar SecureRandom
que genera números aleatorios criptográficamente más seguros).

Para hacer uso de esta clase debemos generar una instancia de la misma y luego utilizar el método nextDouble que genera números en el intervalo [0, 1).

Sin embargo, dicha clase también posee el método nextInt(int) que genera números enteros aleatorios en el intervalo [0, int - 1]. Por tanto, si queremos generar
números enteros en el intervalo [x, y] debemos utilizar el siguiente código:

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 10/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Random generador = new Random();
int numeroAleatorio = generador.nextInt(y - x + 1) + x;

Desde java 8, la clase Random posee varios métodos sobrecargados que nos permiten generar un IntStream que es un flujo de números aleatorios. Por ejemplo, si
queremos simular que tiramos un dado 15 veces podríamos utilizar el siguiente código:

Random generador = new Random();


IntStream flujoNumerosAleatorios = generador.ints(15, 1, 7);
Iterator<Integer> iterador = flujoNumerosAleatorios.iterator();
while (iterador.hasNext()) {
System.out.println(iterador.next());
}

Otra forma sería hacerlo con las funciones lambda de java 8:

Random generador = new Random();


IntStream flujoNumerosAleatorios = generador.ints(15, 1, 7);
flujoNumerosAleatorios.forEach(tirada -> System.out.println(tirada));

Ejercicios
Arrays

Letra del DNI

Escribir un programa java que lea el número de tu DNI y te muestra la letra correspondiente al mismo. Las letras del DNI se calculan quedándonos con el resto
del numero de DNI entre 23 y aplicando esta correspondencia:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
T R W A G M Y F P D X B N J Z S Q V H L C K E

Para ello, inicializa un array con estos valores y calcula la letra de un dni dado.

Mayor y menor

Escribir un programa en java que pida por teclado el número enteros a generar, que deberá ser al menos dos. Cree un array de enteros de ese tamaño y lo
rellene con números aleatorios entre 0 y 1000. Finalmente nos mostrará cuál es el mayor, cuál es el menor y la posición que ocupan.

Media

Escribir un programa java que lea por teclado la cantidad de números a generar, que deberá ser al menos 3. Cree un array de dicho tamaño y los genere
aleatoriamente entre 0 y 100. Debe calcular la media de los mismos como entero y contar la cantidad de números que hay por encima, por debajo y los que son
iguales a la media. También debe anotar las posiciones en las que se encuentran los números iguales a la media. Para ello ve almacenando sus posiciones en un
StringBuffer separados por comas y lo muestre sólo en el caso que haya números iguales. Si solo hay uno no debe mostrar la coma final.

Tablero de ajedrez

Escribir un programa en java que muestre por pantalla un tablero de ajedrez, en el que las casillas blancas estarán representadas por el caracter ‘ ‘ y las negras
por el caracter ‘X’. Para ello primero inicializará el tablero al declararlo y lo mostrará. Luego inicializará el tablero en tiempo de ejecución y lo mostrará.

Estudio estadístico IMC

Escribir un programa java que lea el IMC de una cantidad mayor que 2 de sujetos y nos muestre la media del IMC de dichos sujetos, el nombre del sujeto (o
de uno de los sujetos) con mayor IMC y con menor IMC, la cantidad de sujetos con IMC por encima de la media y por debajo. El IMC de un sujeto se calcula
como la división de su peso en kg. entre el cuadrado de su altura en cm.

Expresiones regulares

Reconocimiento DNI

Escribir un programa que pida por teclado un DNI mientras éste no sea válido. Luego mostrará el número por un lado y la letra por otra. Todo ello lo debe
hacer utilizando expresiones regulares y grupos en las mismas.

Valida teléfono

Escribir un programa en java que valide un teléfono utilizando una expresión regular pero usada en métodos de la clase String.

Prefijos telefónicos

Escribir un programa java que valide si un teléfono introducido cumple el formato de un teléfono español. Puede llevar el prefijo internacional o no. Será
válido si su prefijo provincial es de tres cifras y el número de teléfono de 6 cifras o el prefijo de capital es dos cifras y el número de teléfono de 7 cifras. Cada
campo irá separado por espacios. Finalmente mostrará o el prefijo de provincia o el prefijo de capital.

Reconocer correos en un texto

Escribir un programa en java que reconozca todos los correos electrónicos encontrados en un texto dado y muestre todos aquellos que haya reconocido.

Cuenta palabras

Escribir un programa en java que cuente las palabras que hay en una frase introducida por teclado.

Cuenta vocales

Escribir un programa java que cuente las vocales existentes en una frase introducida por teclado, ignorando mayúsculas y minúsculas.

Ejercicios fechas y tiempos

Ejemplo básico de fechas

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 11/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Crea un programa que imprima la fecha actual en diferentes formatos (corto, largo, de texto, …), que cree un objeto fecha con la fecha de año nuevo del año
pasado mediante el método of y la muestre por pantalla y que cree un objeto con la fecha de liberación de java 8 (18/03/2004) mediante una cadena utilizando
el método parse y posteriormente la muestre por pantalla.

Ejemplo básico de fechas

Crea un programa que imprima la hora actual en diferentes formatos (corto, largo, de texto, …), que cree un objeto tiempo con la hora del mediodía mediante
el método of y la muestre por pantalla y que cree un objeto con la hora de la medianoche mediante una cadena utilizando el método parse y posteriormente la
muestre por pantalla.

Fecha nacimiento y cumpleaños Crea un programa que nos pida la fecha de nacimiento como cadena en un formato válido mientras esta no sea válida. Nos
muestre dicha fecha por pantalla, nos diga qué día de la semana era y nos diga los días que faltan para el mismo, los que han pasado o si hoy es nuestro
cumpleaños en cuyo caso nos felicitará y nos dirá los años que cumplimos.

Ejercicios variados

Baraja de cartas

Debes crear un programa que modele una baraja de cartas y simule que intercambias cartas de posiciones aleatorias n veces y muestre cuántas se han quedado
en la misma posición inicial y el resultado final de la baraja. Para ello te propongo el siguiente diagrama de clases:

Juego de dados

Debes crear un programa en java que simule un juego de dados. El juego de dados consiste en que entre 2 y 10 jugadores lanzarán un dado 5 veces y ganará o
ganarán los que su puntuación haya sido la más alta.

Un dado simplemente se podrá lanzar y en su lanzamiento devolverá un número entero entre 1 y 6, ambos inclusive.

Un jugador tendrá un nombre que no puede estar vacío, sabrá jugar (es decir, sabrá lanzar el dado 5 veces), llevará apuntado el resultado de cada tirada y sabrá
sumar sus tiradas para mostrar su puntuación total obtenido. Además un jugador se mostrará indicando su nombre, sus tiradas y su puntuación total.

El juego de dados tendrá un conjunto de jugadores entre 2 y 10, se podrá jugar lo que hará que cada jugador comience el juego y será capaz de informar de la
puntuación ganadora y de los jugadores que han obtenido dicha puntuación. Un juego de dados se representa, representando cada uno de los jugadores (su
nombre, tiradas y puntuación total), la puntuación ganadora y los jugadores que han obtenido dicha puntuación.

El programa principal preguntará cuántos jugadores quieren jugar, leerá los nombres de cada uno de los jugadores, comenzará la partida y mostrará los
resultados. Para ello debes implementar el diseño que se expone en el siguiente diagrama de clases:

Gestión de clientes

Debes crear un programa para la gestión de clientes de una empresa.

Una dirección postal constará de una dirección, una localidad y un código postal.

Los datos de contacto de un cliente constarán de un teléfono, un correo y una dirección postal.

Los datos personales de un cliente tendrán un nombre, unos apellidos, un dni y una fecha de nacimiento.

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 12/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Los datos de un cliente se compondrán de sus datos personales y sus datos de contacto.

Un cliente será igual a otro si su dni es el mismo.

Deberemos podemos añadir clientes, borrar clientes, buscar clientes, obtener todos los clientes y listar los clientes existentes.

Nunca devolveremos una referencia o nos adueñaremos de la misma en nuestra clase.

Para ello, por ahora debe utilizar arrays.

El programa principal mostrará un menú que nos permitirá realizar todas estas acciones.

Aunque aún no estamos utilizando el patrón MVC, deberás estruturar las clases en una vista y un modelo.

Para todo ello te propongo el siguiente diagrama de clases:

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 13/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/clasesInteresantesJava/
Programación mantenido por IES-Al-Andalus

Ver en GitHub

https://ies-al-andalus.github.io/Programacion/clasesInteresantesJava/ 14/14
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

Programación
Repositorio sobre programación en java del IES Al-Ándalus
Estructuras dinámicas en java
Introducción
Genéricos
Colecciones
Conjuntos
Listas
Mapas
Ejercicios

Estructuras dinámicas en java


En este apartado trataremos las estructuras dinámicas en java. Para ello primero abordaremos la genericidad en java
ya que es el instrumento utilizado para la utilización de las estructuras de datos dinámicas. Seguidamente he
pretendido presentaros las estructuras de datos más utilizadas en java.

Al igual que en otros apartados, el objetivo principal es que tengáis una amplía gama de ejercicios, con sus
respectivas posibles soluciones, sobre estructuras dinámicas en java.

José Ramón Jiménez Reyes

@JRJimenezReyes

joseramon.jimenez@iesalandalus.org

Contenidos

Introducción
Genéricos
Colecciones
Conjuntos
Listas
Mapas
Ejercicios

Introducción
En apartados anteriores hemos tratado estructuras de datos tales como String o Array.

La primera de ellas era una estructura que permitía almacenar una secuencia de caracteres. Pero una de sus
características era que son objetos inmutables. Es decir, que una vez creada no podía cambiar. Además, cada
operación que conllevaba la modificación de la misma lo que realmente hacía era crear una nueva instancia para
albergar el resultado de dicha modificación.

La segunda de ellas era una estructura que permitía almacenar una cantidad prefijada de elementos del mismo tipo. Si
alcanzábamos la cantidad prefijada de elementos, esta estructura no permitía almacenar más elementos.
https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 1/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

Como sabéis, estas estructuras son muy útiles y muy utilizadas, pero adolecen de un gran inconveniente: los tamaños
establecidos en su creación no pueden ser modificados (entre otros inconvenientes). Debido a esto, a estas estructuras
de datos se les suele denominar estructuras de datos estáticas.

En este apartado nos vamos a dedicar a introducir las estructuras dinámicas de datos que, a diferencia de las
anteriores, si pueden modificar su tamaño en tiempo de ejecución, es decir, pueden crecer o decrecer según nuestras
necesidades. Es por ello, que dominarlas es esencial debido a su amplio uso, dadas sus ventajas.

Por ello, empezaremos viendo cómo declarar y utilizar clases genéricas ya que son la base de las estructuras
dinámicas de datos. Luego pasaremos a ver las colecciones y sus principales tipos. Finalmente veremos los mapas. Y
como siempre os mostraré ejemplos de uso de todo ello.

Genéricos
En java, a partir de la versión 5, existen los denominados genéricos. Los genéricos son el mecanismo que nos brinda
java para usar tipos abstractos para definir clases, interfaces y métodos y que luego instanciaremos con un tipo
concreto. Como puedes imaginar, esto también es posible haciendo uso de la clase Object, pero con el uso de
genéricos tenemos muchas ventajas, tales como:

La comprobación de tipos se hace en tiempo de compilación y no tendremos indeseadas ClassCastException


que muchas veces son difíciles de depurar.
Eliminamos el abuso de los casting que hace que nuestro código no sea lo suficientemente legible.
Nos permiten la reutilización de código con tipado seguro, por ejemplo para implementar clases contenedoras
como las colecciones o implementar algoritmos genéricos.

Una clase genérica es una clase que en su definición contiene un parámetro de tipo o una lista de parámetros de tipos,
que serán establecidos cuando ésta sea instanciada. Generalmente estos parámetros son nombrados como E (elemento
de una colección), T (un tipo), K (una clave), V (un valor), etc. aunque nosotros podemos ponerle el nombre que
creamos conveniente. Los parámetros son encerrados entre los signos < y > y si hay más de uno se separan por comas.

Cuando declaramos una clase genérica debemos indicar el parámetro de tipo. A la hora de instanciar una clase
genérica también debemos indicar el parámetro de tipo, aunque a partir de java 7 podemos utilizar el operador
diamante <> y el tipo se inferirá de la declaración.

Veamos un ejemplo muy sencillo, para ver cómo se implementa una clase genérica y cómo se trabaja con ella. Para
ello vamos a declarar una clase que sea capaz de contener cualquier tipo de contenido y para ello utilizaremos una
clase genérica. Veamos su implementación y luego veremos su utilización.

public class Contenedor<T> {

T contenido;

public Contenedor(T contenido) {


this.contenido = contenido;
}

public T get() {
return contenido;
}
}

Como podéis observar, al lado del nombre de la clase hemos declarado el parámetro para el tipo (encerrado entre < y
>). Ese parámetro lo utilizamos dentro de la clase como un tipo y podemos declarar un atributo de ese tipo, pasarlo
como parámetro a un método o devolverlo.

Veamos ahora cómo utilizarla. Para ello la utilizaremos con un Integer y con un String.

public class PruebaContenedor {

public static void main(String[] args) {


Contenedor<Integer> contenedorEntero = new Contenedor<>(1);
System.out.println("Contenido del contenedor entero: " + contenedorEntero.get());

Contenedor<String> contenedorCadena = new Contenedor<>("Hola!");


System.out.println("Contenido del contenedor cadena: " + contenedorCadena.get());

https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 2/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/
}
}

Como se puede ver hemos declarado dos variables de nuestra clase. Una que contendrá Integer y otra que contendrá
String. Para ello, a la hora de la declaración hemos utilizado el nombre de la clase seguida por el tipo que queremos
darle al parámetro de tipo, encerrado entre < y >. Luego hemos llamado al operador new para crearla y seguido al
nombre de la clase hemos utilizado el operador diamante <> que indica que es una clase genérica y que infiere el tipo
del parámetro de tipo de la declaración de la misma en la que sí indicábamos el parámetro de tipo. Como veis, es un
ejemplo que puede tener poca utilidad pero que sí nos sirve para ver cómo declarar y utilizar los genéricos.

Al igual que hemos hecho con las clases, también podemos declarar interfaces genéricas.

También es posible restringir los parámetros de tipo indicando de quién deben extender, aunque no entraremos en
detalle por no complicar más las cosas.

Los genéricos tienen algunas restricciones, como pueden ser:

No podemos instanciar tipos genéricos con tipos primitivos. Para ello debemos utilizar las clases envoltorio
como Integer, Float, Double, etc. El compilador transforma automáticamente los tipos primitivos en sus clases
envoltorio y viceversa, es el mecanismo conocido como autoboxing.
Sobre un parámetro de tipo sólo podemos aplicar métodos públicos de la clase Object (el método clone no es
público en la clase Object).
No se pueden crear instancias de los parámetros de tipo.
No se pueden crear arrays de los parámetros de tipo.
Tampoco se pueden declarar campos static de dichos tipos.

También podemos crear métodos genéricos, incluso en clases que no son genéricas. Para ello, antes del tipo que
devuelve el método, debemos indicar el parámetro de tipo que se utilizará en el método (deberá estar encerrado entre
< y >). Cualquier método con un parámetro de tipo se denomina método genérico. Veamos un ejemplo:

public class MetodoGenerico {

public static <T> String separarPorComas(List<T> elementos) {


StringBuilder elementosSeparadosPorComas = new StringBuilder();
for (T elemento : elementos) {
elementosSeparadosPorComas.append(elemento).append(',');
}
if (elementosSeparadosPorComas.length() > 0) {
elementosSeparadosPorComas.deleteCharAt(elementosSeparadosPorComas.length() - 1);
}
return elementosSeparadosPorComas.toString();
}

public static void main(String[] args) {


List<Integer> numeros = new ArrayList<>();
numeros.add(1);
numeros.add(2);
numeros.add(3);
System.out.println(separarPorComas(numeros));

List<String> cadenas = new ArrayList<>();


cadenas.add("Hola");
cadenas.add("que");
cadenas.add("tal");
System.out.println(separarPorComas(cadenas));
}
}

Pues ya conoces algo más sobre los genéricos que, aunque simplemente ha sido una pequeña introducción, te servirá
para comprender perfectamente el funcionamiento y uso de las estructuras dinámicas de datos en java que será lo que
veremos a continuación.

Colecciones
Una colección es un almacén de objetos de la misma clase y que utilizamos en nuestros programas para poder
gestionarlos correctamente. Es muy común añadir nuevos objetos al almacén, buscar si existe un objeto en el

https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 3/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

almacén, listar todos los objetos del almacén, borrar un objeto del mismo, etc. Pues a este almacén le llamamos
colección y a los objetos que se almacenan en él, ya que son objetos genéricos, les llamamos elementos.

Es común hablar de los elementos que se almacenan en la colección como objetos de dominio u objetos DTO
(Direct Transfer Object) debido al nombre de su patrón de diseño en el que se basa. También es común llamar a las
colecciones de dichos objetos como objetos de negocio u objetos DAO (Direct Access Object) también debido al
nombre de su patrón de diseño.

Java nos ofrece una API completa para trabajar con estas estructuras. Todo está perfectamente jerarquizado y se
estructura en interfaces que definen las operaciones básicas y luego diferentes implementaciones que pueden ser más
aconsejables para ciertas situaciones pero desaconsejables para otras.

La interfaz Collection es la que define las operaciones básicas de toda la jerarquía y que resumidamente podemos
representar en este diagrama de clases. Ni que decir tiene que simplemente he mostrado las más usuales, pero hay
muchas más y que esta jerarquía es una versión resumida de la verdadera jerarquía del API de java.

Veamos qué es lo que la interfaz Collection define y que obliga a que todas sus descendientes en la jerarquía deban
implementar.

Pasemos a ver cuál debe ser el comportamiento de los principales métodos declarados en esta interfaz que,
recordemos, aún no han sido implementados por ninguna clase.

Método Descripción
add Añade un elemento a la colección. Devuelve un valor lógico indicando si se ha podido añadir o no.
clear Elimina todos los elementos de la colección.
contains Informa si el elemento pasado por parámetro está incluido en la colección o no.
isEmpty Informa si la colección está vacía o no.
Intenta eliminar el elemento pasado por parámetro. Devuelve un valor lógico indicando si se ha podido
remove
eliminar o no.
size Devuelve el número de elementos almacenados en la colección.

https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 4/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

Como hemos comentado, la interfaz define el comportamiento genérico, pero dependerá de la implementación el
comportamiento específico.

Conjuntos
Un conjunto es una colección de elementos en la que no hay elementos repetidos. Los conjuntos están
implementados por la interfaz Set y SortedSet, que podemos ver en el siguiente diagrama de clases.

Como se puede apreciar, la interfaz Set no añade ninguna nueva funcionalidad a las colecciones. Sin embargo, la
interfaz SortedSet sí añade unas cuantas funcionalidades mínimas a las colecciones.

Dado que la interfaz Set no añade nueva funcionalidad, tenemos el inconveniente que de un conjunto no podemos
recuperar un elemento de una forma trivial. Para ello deberíamos utilizar un iterador y recorrerlo hasta encontrarlo.
Además, cabe destacar que cuando recorremos un conjunto, el orden no tiene nada que ver con el orden de inserción.

Las dos implementaciones directas de Set son HashSet y LinkedHashSet. Ambas implementan una tabla hash o de
dispersión, aunque la segunda también utiliza nodos enlazados. El caso es que para determinar si un elemento
pertenece al conjunto o no se basan tanto en el método equals como en el método hashCode, dependiendo de la
situación. Recordad que ya dijimos que ambos métodos debían ser consistentes y que si dos objetos eran iguales su
código hash debía ser el mismo.

Dado que un conjunto no puede almacenar elementos repetidos, el método add devuelve el resultado real de la
operación dado que si existe el elemento a añadir devolverá false ya que no ha podido añadirlo y devolverá true si el
elemento no existía.

La clase TreeSet implementa la interfaz SortedSet que es una colección de elementos no repetidos y ordenados.
Esta implementación se basa en árboles binarios de búsqueda. Requiere que la clase que contienen tenga definida una
ordenación, bien sea porque la clase implementa la interfaz Comparable o porque le pasamos una instancia de la clase
Comparator en el constructor. Hablaremos de esto más adelante.

https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 5/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

Listas
Las listas son secuencias de elementos a las que podemos acceder por su posición. La posición va desde 0 hasta la
longitud - 1. Siempre se añade al final y como sí acepta elementos repetidos, el método add siempre devuelve true ya
que siempre puede añadir el elemento.

La interfaz List añade nuevas funcionalidades a la interfaz Collection para poder permitir operar con los índices.
Esto lo podemos ver claramente en el siguiente diagrama de clases.

Las principales operaciones que añade la interfaz List son las siguientes:

Método Descripción
add(int i, E e) Añade el elemento e en la posición i y desplaza los demás elementos a la derecha.
E get(int i) Devuelve el elemento que ocupa la posición i.
int indexOf(E e) Devuelve el índice que ocupa el elemento e o -1 en caso de que no exista.
E remove(int i) Elimina el elemento que ocupa la posición i.
E set(int i, E e) Modifica el elemento que ocupa la posición i, poniéndolo a e.

La interfaz List es implementada por dos clases:

ArrayList es una implementación basada en arrays redimensionables, es decir, si necesita crecer, crea un array
de mayor capacidad y copia los elementos. Como puedes imaginar, esta operación es bastante costosa, por lo
que debemos tenerlo en cuenta. Pero esta implementación también adolece de otros problemas:
Cuando insertamos un elemento en medio, debe desplazar todos los elementos a partir de esa posición
hacia la derecha.
https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 6/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

Cuando eliminamos un elemento de en medio, debe desplazar todos los elementos desde dicha posición
una posición a la izquierda.
LinkedList es una implementación basada en nodos doblemente enlazados. Esta implementación es muy
eficiente con las operaciones realizadas en los extremos, pero no pasa lo mismo con las posiciones intermedias.

Recorridos
La forma adecuada de declarar una lista o un conjunto es utilizando la interfaz (List o Set) en la declaración y así
podremos cambiar su implementación en cualquier momento sin modificar nuestro código. Esto es válido a la hora de
declarar parámetros de un método o el valor que devuelve. En la declaración debemos indicar la clase que va albergar
entre < y >. Para su creación utilizaremos el operador new seguido por el constructor de la clase que queremos utilizar
(su implementación) seguida del operador diamante <>.

...
Set<Integer> primos = new HashSet<>();
...
List<String> nombres = new ArrayList<>();
...

Para su recorrido podremos utilizar el bucle foreach que ya conocemos:

...
for (Integer primo : primos) {
System.out.println(primo);
}
...
for (String nombre : nombres) {
System.out.println(nombre);
}
...

Pero también podemos utilizar un iterador. Un iterador es un objeto genérico que nos permite recorrer los elementos
de una colección de una forma uniforme para todas ellas. Un iterador posee tres métodos:

boolean hasNext() indica si quedan más elementos en el iterador o no.


T next() devuelve el siguiente elemento del iterador.
void remove() elimina el último elemento devuelto por el iterador, tanto del iterador como de la colección.

Por tanto podemos utilizar un iterador para recorrer una colección. En este primer ejemplo utilizaremos un bucle for:
...
for (Iterator<Integer> iterador = primos.iterator(); iterador.hasNext();) {
int primo = iterador.next();
System.out.println(primo);
}
...
for (Iterator<String> iterador = nombres.iterator(); iterador.hasNext();) {
String nombre = iterador.next();
System.out.println(nombre);
}
...

O también podíamos haber utilizado un bucle while si nos parece más apropiado:

...
Iterator<Integer> iterador = primos.iterator();
while (iterador.hasNext()) {
int primo = iterador.next();
System.out.println(primo);
}
...
Iterator<String> iterador = nombres.iterator();
while (iterador.hasNext()) {
String nombre = iterador.next();
System.out.println(nombre);
}
...

Ordenaciones
https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 7/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

Para ordenar una lista (o que un SortedSet se ordene como nosotros queremos) tenemos varias posibilidades.

Primera posibilidad

Lo más fácil sería que los elementos pertenezcan a una clase que tiene definido un orden, es decir, que implementa la
interfaz Comparable<T>. Esta interfaz es una interfaz genérica y que obliga a definir el método int compareTo(<T>
otro) que devuelve:

Un número negativo si el elemento pasado por parámetro es menor que el actual.


0 si ambos elementos son iguales.
Un número positivo si el elemento pasado por parámetro es mayor que el actual.

Si la clase de los elementos implementa esta interfaz, podremos utilizar la clase de utilidades Collections, por medio
de su método sort que, en una primera implementación, recibe una lista cuyos elementos implementan la interfaz
Comparable y que ordena dicha lista.

Si una clase implementa la interfaz Comparable éste será el orden que utilizará un TreeSet para mantener sus
elementos ordenados.

Segunda posibilidad

Podemos utilizar una clase que implemente la interfaz Comparator<T> que obliga a definir el método int compare(T
elemento1, T elemento2) que devuelve:

Un número negativo si elemento1 es menor que elemento2.


0 si ambos elementos son iguales.
Un número positivo si elemento1 es mayor que elemento2.

Podemos crear una clase para tal efecto o simplemente crear una clase anónima, que sería algo más aconsejable. Por
ejemplo, queremos ordenar una lista de cadenas primero por su longitud y después por su orden alfabético.
...
Collections.sort(palabras, new Comparator<String>() {

@Override
public int compare(String elemento1, String elemento2) {
return (elemento1.length() == elemento2.length()) ?
elemento1.compareTo(elemento2) :
Integer.compare(elemento1.length(), elemento2.length());
}

});
...

Aunque la forma idónea de hacerlo sería utilizando las funciones lambda, dado que Comparator es un interfaz
funcional, de la siguiente forma:
Collections.sort(palabras, (elemento1, elemento2) -> (elemento1.length() == elemento2.length()) ?
elemento1.compareTo(elemento2) :
Integer.compare(elemento1.length(), elemento2.length())
);

En el constructor de un TreeSet también es posible utilizar una clase que implemente la interfaz Comparator.

Pero la interfaz Comparator también define algunos métodos estáticos que nos pueden ayudar a crear nuestras propias
ordenaciones de una forma sencilla e incluso encadenar comparaciones.

...
Collections.sort(palabras, Comparator.comparing(String::length).thenComparing(String::compareTo));
...

Los tres ejemplos anteriores realizan la misma ordenación de nuestra lista, pero podemos apreciar la simplicidad y
elegancia de esta última forma.

También podíamos haber utilizado el método sort de la interfaz List que acepta como parámetro un Comparator.

https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 8/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/
...
palabras.sort(Comparator.comparing(String::length).thenComparing(String::compareTo));
...

Mapas
Los mapas son estructuras de datos dinámicas en las que se puede acceder a los valores de los elementos mediante
una clave, en vez de por su índice como hacíamos con las listas. Es importante aclarar que todas las claves deben ser
diferentes, es decir, que no puede existir dos claves repetidas. Son también conocidos como diccionarios o arrays
asociativos. A la hora de declararlos debemos utilizar un par de clases, indicando la clase que hará de clave y la que
hará de valor <K, V>. Veamos su jerarquía:

Vista la jerarquía, veamos la funcionalidad que fuerza la interfaz Map.

El comportamiento de los principales métodos debería ser el siguiente:

Método Descripción
void clear() Elimina todas las entradas del mapa.
boolean containsKey(Object
Indica si el mapa contiene una entrada con la clave especificada.
key)
boolean containsValue(Object
Indica si el mapa contiene una entrada con dicho valor (o varias).
value)
Set<Entry<K, V>> entrySet() Devuelve un conjunto de entradas, es decir, de pares clave-valor.
Devuelve el valor de la entrada cuya clave es la especificada o null si no existe
V get(Object key)
una entrada con dicha clave.
boolean isEmpty() Indica si el mapa está vacío o no.
Set<K> keySet() Devuelve el conjunto de claves del mapa.
V put(K key, V value) Establece el valor de la entrada asociada a la clave especificada.
V remove(Object key) Elimina la entrada asociada a la clave indicada.
int size() Devuelve el número de entradas que contiene el mapa.
Devuelve una colección cuyo contenido son todos los valores asociados a las
Collection<V> values()
entradas del mapa.
https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 9/10
9/7/23, 20:23 ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/

Las implementaciones de la interfaz Map son las siguientes (incluyo la implementación de SortedMap):

HashMap Implementación de un mapa utilizando una tabla hash.


EnumMap Implementación de un mapa utilizando arrays en la que las claves deben ser enumerados. En el
constructor debemos indicar la clase del enumerado que utilizaremos de clave.
TreeMap Implementación basada en árboles binarios de búsqueda balanceados y que implementa la interfaz
SortedMap.

Para recorrer un mapa debes hacerlo utilizando alguna de las vistas que nos ofrece:

El conjunto de claves keySet().


La colección de valores values().
El conjunto de entradas entrySet().

Si utilizamos un Iterator con cualquiera de estas vistas, podremos eliminar elementos del mapa cuando lo
recorremos.

Imagina que queremos simular la tirada de un dado y contar cuántas veces sale cada una de ellas y que nos muestre
sus ocurrencias de forma ordenada. Para ello podemos utilizar un mapa ordenado cuyas entradas serán un par de
enteros: la clave representará la cara y el valor representara el número de veces que ha salido dicha cara.

private static final int CANTIDAD = 100;


private static final int CARAS = 6;

...
Random generador = new Random();
List<Integer> numeros = new ArrayList<>();
for (int i = 0; i < CANTIDAD; i++) {
numeros.add(generador.nextInt(CARAS) + 1);
}
Map<Integer, Integer> ocurrencias = new TreeMap<>();
for (Integer numero : numeros) {
ocurrencias.put(numero, ocurrencias.containsKey(numero) ? ocurrencias.get(numero) + 1 : 1);
}
System.out.println(ocurrencias);
...

Ejercicios
Cola de nodos enlazados

Implementar una cola genérica que utilice nodos enlazados, que implemente los métodos encolar y desencolar
y que no acepte valores nulos.

Lista sin elementos repetidos

Implementar una lista genérica que no acepte elementos repetidos, que inserte por el final y que permita borrar
un elemento.

Diccionario

Implementar un diccionario que almacene las iniciales ordenadas de las diferentes entradas que contiene, que
también estarán ordenadas.

Programación mantenido por IES-Al-Andalus

Ver en GitHub

https://ies-al-andalus.github.io/Programacion/estructurasDinamicasJava/ 10/10

También podría gustarte