Temario Completo de Programacion - Java
CFGS - Todas las Unidades Didacticas
UD1 - Lenguajes de programacion
Conociendo los lenguajes de programacion
Un lenguaje de programacion es un conjunto de simbolos y caracteres combinados entre si, de acuerdo con una sintaxis ya definida y respetando unas reglas establecidas para posibilitar la comunicacion con la CPU del ordenador.
Este proceso implica tres niveles de abstraccion.
Tipos de lenguajes de programacion
La clasificacion de los lenguajes de programacion no es unica. Existen varias:
- Lenguajes de alto y bajo nivel.
- Lenguajes interpretados y compilados.
- Lenguajes orientados o no a objetos.
- Lenguajes de proposito cientifico u otros.
Ejemplos de lenguajes de alto nivel:
- Java: de alto nivel, de proposito general, orientado a objetos, compilado e independiente de la plataforma.
- R: de alto nivel, de proposito estadistico, orientado a objetos, interpretado e independiente del sistema operativo.
- PHP: de alto nivel, de uso general, para plataformas web, orientado a objetos, interpretado del lado del servidor.
Ejemplos de lenguajes de bajo nivel:
- Ensamblador: de bajo nivel que implementa una representacion simbolica de los codigos de maquina binarios.
- Lenguaje maquina: de bajo nivel, interpretado directamente por el microprocesador.
1. Estructura de un programa informatico
Un programa informatico es una secuencia de acciones (instrucciones) que manipulan un conjunto de elementos (datos) para obtener la solucion (salida) a un requerimiento o necesidad.
Existen dos partes o bloques principales que componen un programa:
- Bloque de declaraciones + asignaciones: se detallan todos los elementos que utiliza el programa (variables o constantes a ser usadas, su tipo y su valor inicial).
- Bloque de instrucciones (entradas + control): acciones u operaciones que se han de llevar a cabo para conseguir los resultados esperados como salida.
El bloque de instrucciones esta compuesto a su vez por tres partes:
- Entrada de datos: instrucciones que almacenan en la memoria interna datos procedentes de un dispositivo externo.
- Proceso o algoritmo: instrucciones que modifican los objetos de entrada y, en ocasiones, creando otros nuevos.
- Salida de resultados: conjunto de instrucciones que toman los datos finales de la memoria interna y los envian a los dispositivos externos.
2. Pseudocodigos y diagramas de flujo (DFD)
Una vez se han analizado los requisitos del programa podemos crear algoritmos para indicar que pasos debe realizar dicho programa, de modo que realice correctamente su proposito (independientemente del lenguaje de programacion que sea usado).
Los algoritmos pueden representarse de tres modos: usando lenguaje natural, pseudocodigo o con Diagramas de Flujo de Datos (DFD), siendo los dos ultimos metodos los mas usados.
4. Fundamentos del lenguaje Java
Conociendo mas sobre Java
Java es un lenguaje de programacion que se popularizo a partir del lanzamiento de su primera version comercial de amplia difusion, la JDK 1.0 en 1996. Actualmente es uno de los lenguajes mas usados para la programacion en todo el mundo.
Maquina Virtual Java (JVM). Compilador e interprete. Bytecode.
El primer concepto a abordar es el de compilacion. "Compilar" significa traducir el codigo escrito en "lenguaje entendible por humanos" a un codigo en "lenguaje maquina".
Los programas Java no se ejecutan en nuestra maquina real sino que se simula una "maquina virtual" con su propio hardware y sistema operativo. Del codigo fuente se pasa a un codigo intermedio denominado bytecode entendible por la maquina virtual Java. Y es esta maquina virtual simulada denominada Java Virtual Machine (JVM) la encargada de interpretar el bytecode dando lugar a la ejecucion del programa.
Esto permite que un programa escrito en Java pueda ejecutarse en una maquina con el Sistema Operativo MacOS, Windows, Linux o cualquier otro, porque en realidad no va a ejecutarse en ninguno de los sistemas operativos, sino en su propia maquina virtual.
Los archivos encargados de ejecutar estas tareas son:
- El compilador → javac. Se encarga de compilar el codigo fuente y crear el archivo .class.
- El interprete → java. Se encarga de interpretar y ejecutar los archivos .class (bytecode).
5. Control de excepciones (try-catch) - Nivel basico
Si introduces un texto cuando un programa pide un numero, el programa falla con java.util.InputMismatchException.
Para controlar este tipo de errores se utiliza el "control de excepciones", el cual debemos incluir cada vez que pidamos un numero y necesitemos que sea un valor numerico.
La estructura es la siguiente:
try {
int x = entrada.nextInt();
} catch (InputMismatchException e) {
System.out.println("Introduce un numero valido.");
}
Para atrapar las excepciones debemos encerrar en un bloque try las instrucciones que generan excepciones. Todo bloque try requiere que sea seguido por un bloque catch.
Los ejemplos mas comunes de excepciones son:
- Tratar de convertir a entero un String que no contiene valores numericos.
- Tratar de dividir por cero.
- Abrir un archivo de texto inexistente o bloqueado por otra aplicacion.
- Conectar con un servidor de bases de datos que no se encuentra activo.
UD2 - Control de excepciones (ampliacion)
Las excepciones en Java son eventos anomalos de cualquier tipo que pueden ocurrir durante la ejecucion de un programa y que alteran el flujo normal de ejecucion. Estos eventos representan situaciones inesperadas o errores que deben ser manejados de manera adecuada.
Ademas de controlar el formato de numeros enteros, es muy tipico necesitar controlar tambien:
- Tratar de convertir a entero un String que no contiene valores numericos.
- Tratar de dividir por cero.
3. Estructuras de control condicional
Las estructuras condicionales sirven para la toma de decisiones en los algoritmos: si ocurre algo entonces ejecutamos unas sentencias y en caso contrario ejecutamos otras.
Existen tres tipos basicos de estructuras condicionales: las simples, las dobles y las multiples.
3.1. Estructuras condicionales simples (if-then)
if (condicion a evaluar) {
//sentencias a realizar
}
if (variable), Java pregunta si el valor de dicha variable es true (tipo booleano). Para la condicion contraria: if (!variable).
3.2. Estructuras condicionales dobles (if-then-else)
if (condicion a evaluar) {
//sentencias si se cumple la condicion
} else {
//sentencias si no se cumple la condicion
}
3.3. Estructuras condicionales multiples (if-then-else-if y switch-case)
Ejemplo de if-else-if multiple (Generaciones):
if (anyo_nac >= 1900 && anyo_nac <= 1927) {
System.out.println("Eres de la Generacion sin bautizar");
} else if (anyo_nac >= 1928 && anyo_nac <= 1944) {
System.out.println("Eres de la Generacion Silent");
} else if (anyo_nac >= 1945 && anyo_nac <= 1964) {
System.out.println("Eres de la Generacion Baby Boomers");
} else if (anyo_nac >= 1965 && anyo_nac <= 1981) {
System.out.println("Eres de la Generacion X");
} else if (anyo_nac >= 1982 && anyo_nac <= 1994) {
System.out.println("Eres de la Generacion Millenial");
} else if (anyo_nac >= 1995 && anyo_nac <= anyo_actual) {
System.out.println("Eres de la Generacion Centennials");
} else {
System.out.println("No eres de ninguna generacion");
}
Sentencia switch-case
El switch-case permite comparar una variable ante diversos posibles valores:
switch (variable a evaluar) {
case value1:
// sentencias
break;
case value2:
// sentencias
break;
default:
// sentencias para cualquier otro valor
}
Ejemplo:
switch(i) {
case 0:
System.out.println("i es cero.");
break;
case 1:
System.out.println("i es uno.");
break;
case 2:
System.out.println("i es dos.");
break;
case 3:
System.out.println("i es tres.");
break;
default:
System.out.println("i no es 0, 1, 2 ni 3");
}
Agrupar casos:
int x = 4;
switch(x) {
case 0:
case 1:
System.out.println("Rango entre 0 y 1");
break;
case 2:
case 3:
System.out.println("Rango entre 2 y 3");
break;
case 4:
case 5:
case 6:
System.out.println("Rango entre 4 y 6");
break;
default:
System.out.println("otro rango");
}
4. Estructuras de control repetitivas (bucles)
4.1. Sentencia while
Esta sentencia hace que una parte del programa se repita mientras se cumpla una cierta condicion:
while (i < 10) {
System.out.println("WHILE"); // Se ejecuta 10 veces
i++;
}
4.2. Sentencia do-while
La condicion se comprueba al final, lo que quiere decir que las sentencias intermedias se realizaran al menos una vez:
Scanner teclado = new Scanner(System.in);
int num;
do {
System.out.println("Introduce un numero distinto de cero para seguir en el bucle");
num = teclado.nextInt();
} while (num != 0);
Comparacion con while forzando al menos una vuelta:
// Con while (forzando entrada)
boolean err = true;
while (err) {
// pedir datos
if (dato_ok) {
err = false;
}
}
// Con do-while (mas limpio)
do {
// pedir datos
} while (dato_no_ok);
4.3. Sentencia for
Se emplea sobre todo para conseguir un numero concreto de repeticiones:
for (valor_inicial; condicion_continuacion; incremento) {
sentencias;
}
Debemos indicar tres ordenes separadas por puntos y coma:
- La primera da el valor inicial a una variable de control.
- La segunda es la condicion del bucle.
- La tercera modifica la variable de control.
for (int i = 0; i < 10; i++) { ... } // 10 pasadas
Mas ejemplos:
for (int i = 10; i >= 0; i -= 2) {
System.out.println(i);
}
// Salida: 10, 8, 6, 4, 2, 0
for (int j = 1; j < 10; j += 2) {
System.out.println(j);
}
// Salida: 1, 3, 5, 7, 9
BONUS: sentencias break y continue
break: rompe la iteracion del bucle, y el programa sale de el inmediatamente:
int numero = 4557888;
int digitos = 0;
while (numero > 0) {
numero /= 10;
digitos++;
if (digitos == 5) break;
}
continue: salta las instrucciones que quedan por realizar, pero pasa a la siguiente iteracion:
for (int i = 1; i <= 10; i++) {
System.out.println("Vuelta: " + i);
if (i == 8) continue;
System.out.println("Terminada vuelta: " + i);
}
// No se mostraria "Terminada vuelta: 8"
5. Trazas de codigo
La traza de un programa sirve para comprobar si el codigo funciona como se espera. Consiste en obtener el valor que van tomando las variables segun se ejecutan las instrucciones, e indicar lo que se va mostrando por pantalla. Las trazas se realizan en papel.
Como se hace una traza?
Se crea una tabla con todas las variables que intervienen en el codigo, con sus valores iniciales. Si el codigo muestra algo por pantalla, ademas tendremos una columna de salida.
Ejemplo:
int a = 10, i;
for (i = a; i <= 20; i += 3) {
System.out.println(i * 2);
}
| a | i | salida |
|---|---|---|
| 10 | 10 | 20 |
| 13 | 26 | |
| 16 | 32 | |
| 19 | 38 | |
| 22 |
BONUS: El operador ternario en Java
variable = condicion ? resultado_si_cierto : resultado_si_falso
Se indica la condicion seguida por ?, despues el valor si se cumple (true), luego : y finalmente el valor si no se cumple (false).
x = a == 10 ? b * 2 : a;
// Equivale a:
if (a == 10) {
x = b * 2;
} else {
x = a;
}
Se pueden concatenar ternarias:
int a = 1, b = 10, x;
x = a == 10 ? b * 2 : b == 10 ? 100 : a; // 100
Uso comun para formatear frases:
System.out.println("La letra A ha aparecido " + cantidad + " " +
(cantidad == 1 ? "vez" : "veces"));
BONUS: Clase Random
La clase Random de Java genera valores aleatorios:
Random random = new Random();
int aleatorio = random.nextInt(); // rango completo
aleatorio = random.nextInt(100); // [0, 99]
aleatorio = random.nextInt(100) + 1; // [1, 100]
Lo mas habitual es usar .nextDouble() que devuelve un valor en el rango [0, 1):
// Formula: (int) random.nextDouble() * cantidad_numeros + termino_inicial
aleatorio = (int)(random.nextDouble() * 100 + 0); // [0, 99]
aleatorio = (int)(random.nextDouble() * 100 + 1); // [1, 100]
Math.random()
Tambien existe Math.random() que devuelve un double [0, 0.999...]. Bajo manga usa la clase Random. Random es superior en jerarquia, permitiendo booleanos, bytes, dobles, flotantes y enteros aleatorios.
Random rnd = new Random();
int aleatorio = rnd.nextInt();
aleatorio = rnd.nextInt(100);
float ale = rnd.nextFloat();
double ale2 = rnd.nextDouble();
long ale3 = rnd.nextLong();
boolean ale4 = rnd.nextBoolean();
aleatorio = rnd.nextInt(100) + 1;
ale2 = Math.random();
Trabajando con cadenas de caracteres en Java (String)
Desde el punto de vista de la programacion diaria, uno de los tipos de datos mas importantes de Java es String. String define y admite cadenas de caracteres.
En Java, los String son objetos. Cuando creamos un literal de cadena (entre comillas), en realidad estamos creando un objeto String:
System.out.println("En Java, los String son objetos");
El texto automaticamente se convierte en un objeto String.
UD3 - Arrays (vectores)
Que es un Array?
Un array se puede definir como una coleccion de datos (de tamano fijo) que contiene valores del mismo tipo.
2. Ordenacion de arrays
Por que ordenar los datos? Es mucho mas eficiente trabajar con datos ordenados.
Que podemos ordenar? Cualquier estructura con elementos ordenables: numeros, caracteres u objetos.
Algoritmo de Seleccion (intercambio)
Consiste en recorrer el array y comparar cada elemento con todos los siguientes para buscar el apropiado con el que intercambiarlo.
De forma ascendente (de menor a mayor)
int array[] = {4, 6, 2, 8, 7};
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[i]) {
int aux = array[j];
array[j] = array[i];
array[i] = aux;
}
}
}
Explicacion del algoritmo:
- El for externo recorre todos los elementos desde el primero hasta el penultimo (
array.length - 1). El dato en la posicionies el dato de referencia. - El for interno recorre todos los elementos posicionados despues del dato de referencia (a la derecha de
i). - El if compara: si encontramos en posicion
jalgun dato menor al de referencia, lo intercambiamos con una variable auxiliar.
De forma descendente (de mayor a menor)
Exactamente igual, pero cambiando la condicion del if:
if (array[j] > array[i]) { // en vez de <
int aux = array[j];
array[j] = array[i];
array[i] = aux;
}
3. Matrices (arrays bidimensionales)
Una matriz o array multidimensional es aquel que para acceder a una posicion concreta, se utiliza una secuencia de varios indices (m[2][4]).
La sintaxis es como la de los arrays, pero con corchetes adicionales:
tipo[][] nombre = new tipo[filas][columnas];
Una matriz se puede interpretar como una tabla cuya primera dimension son las filas y la segunda son las columnas. Tambien se puede ver como una representacion de diferentes vectores juntos.
4. Tratamiento de vectores en matrices
Opcion facil: vector en fila conocida
int vector[] = {3, 5, 4, 1};
int matriz[][] = new int[2][4];
for (int i = 0; i < matriz[0].length; i++) {
matriz[0][i] = vector[i];
}
// Salida: 3 5 4 1 / 0 0 0 0
Opcion 2: vectores por teclado
Scanner teclado = new Scanner(System.in);
int matriz[][] = new int[4][4];
for (int i = 0; i < matriz.length; i++) {
System.out.print("Ingresa el vector: ");
String[] lectura = teclado.next().split(",");
for (int j = 0; j < matriz[i].length; j++) {
matriz[i][j] = Integer.parseInt(lectura[j]);
}
}
Comparar vector con fila de matriz
int vector[] = {1, 4, 5, 8};
int matriz[][] = {{3,2,5,4}, {1,4,5,8}, {9,4,3,5}};
System.out.println("Comprobando fila con indice 1");
for (int i = 0; i < matriz[1].length; i++) {
if (matriz[1][i] != vector[i]) {
System.out.println("No son iguales.");
return;
}
}
System.out.println("Son iguales.");
Comparar vector con columna de matriz
int vector[] = {4, 8, 5};
int matriz[][] = {{3,2,5,4}, {1,4,5,8}, {9,4,3,5}};
System.out.println("Comprobando columna con indice 3");
for (int i = 0; i < matriz.length; i++) {
if (matriz[i][3] != vector[i]) {
System.out.println("No son iguales.");
return;
}
}
System.out.println("Son iguales.");
Eliminar duplicados de un array con .distinct()
int vector[] = {3, 3, 7, 8, 8, 9, 10, 15, 15};
int vector2[] = Arrays.stream(vector).distinct().toArray();
System.out.println(Arrays.toString(vector)); // [3, 3, 7, 8, 8, 9, 10, 15, 15]
System.out.println(Arrays.toString(vector2)); // [3, 7, 8, 9, 10, 15]
BONUS: Etiquetar bucles anidados
En Java, las etiquetas se pueden usar con continue y break. Se colocan antes de la sentencia a etiquetar seguida de :. Son utiles cuando tenemos bucles anidados y queremos especificar en cual hacer un break o continue.
bucle1:
for (int i = 0; i < 10; i++) {
bucle2:
for (int j = 0; j < 10; j++) {
bucle3:
for (int k = 0; k < 10; k++) {
if (i == j && j == k) {
break bucle2; // sale de bucle2 y bucle3
}
}
}
}
Otro ejemplo:
boolean esVerdadero = true;
externo:
for (int i = 0; i < 5; i++) {
while (esVerdadero) {
System.out.println("Hola!");
break externo; // rompe el for, no solo el while
}
System.out.println("Despues del while!");
}
System.out.println("Despues del for!");
UD4 - Estructura de un programa Java (metodos)
En muchas ocasiones se tiene que usar un mismo bloque de codigo para resolver un mismo problema con datos diferentes. Escribir el mismo codigo varias veces es un trabajo repetitivo, improductivo, y dificil de mantener. La mejor forma es construir a partir de piezas mas pequenas (subprogramas). En Java, esto se hace usando metodos.
Los metodos son utilizados para evitar la repeticion de codigo. Se pueden ejecutar desde varios puntos de un proyecto para reutilizar el codigo.
Un metodo es un bloque de codigo que:
- Se invoca desde algun punto del codigo del programa principal.
- Puede emplear datos externos (parametros). Si proporcionamos variables como parametros, siempre se proporcionara una copia (paso por valor).
- Devolvera (return) un resultado calculado. Hay metodos que no devuelven nada (void).
4.2. Ambitos de una variable
Variables locales: solo se pueden usar en el metodo en el que se declaran. Los parametros de un metodo se consideran variables locales del mismo.
Variables globales: se declaran fuera del cuerpo de un metodo (dentro del class). De momento usaremos la directiva static.
- Legibilidad menor.
- Efectos colaterales al modificar su valor dentro de un metodo.
- No potencia el paso de informacion entre metodos.
Para protegernos, lo logico es crearlas como constantes:
public class PruebaVariables {
static final double PI = 3.1415926535897932384626433832795;
}
4.4. Recursividad
La recursividad es una forma de resolver un problema mediante una funcion que se llama a si misma hasta que se cumpla una determinada condicion.
Para que un problema pueda ser resuelto mediante recursividad han de cumplirse 3 condiciones:
- El problema debe tener una salida recursiva.
- El problema debe tener una salida no recursiva.
- El problema debe reducirse en cada nueva llamada a la funcion.
public static void funcion() {
// codigo
if (/*condicion*/) {
return funcion(); // SALIDA RECURSIVA
} else {
return; // SALIDA NO RECURSIVA
}
}
Ejemplo: calculo del factorial
// 3! = 3 x 2 x 1 = 6
// 4! = 4 x 3! = 24
public static int factorial(int n) {
if (n == 0 || n == 1) return 1; // salida no recursiva
else return n * factorial(n - 1); // salida recursiva
}
// En main:
int n = factorial(4);
System.out.println(n); // 24
La recursion hace un uso intensivo de la pila de llamadas, utilizada por la JVM para implementar la ejecucion de las llamadas a metodos.
BONUS: Javadoc
Documentar un proyecto es fundamental de cara a su futuro mantenimiento. Javadoc es una utilidad de Oracle para la generacion de documentacion en formato HTML a partir de codigo fuente Java.
En IntelliJ IDEA, escribir /** justo antes de la cabecera de un metodo y presionar enter genera automaticamente un fragmento con etiquetas @param y @return.
Etiquetas principales:
@param: describe un parametro del metodo.@return: describe lo que devuelve el metodo.@author: autor de la clase (solo para clases).@version: version de la clase (solo para clases).@see: referencia a pagina web o clase relacionada.
Para generar el HTML: Tools → Generate JavaDoc... en IntelliJ IDEA.
UD5 - Introduccion a la POO
La programacion orientada a objetos (POO) supone un nuevo paradigma frente a la programacion estructurada o modular. En la POO, los datos y metodos se agrupan formando un bloque que se denomina clase.
Ejemplo: programacion modular vs POO
Programacion modular (Triangulo):
public class Triangulo {
public static void main(String[] args) {
double base = leerValor("Introduce la base: ");
double altura = leerValor("Introduce la altura: ");
double area = calcularArea(base, altura);
mostrarResultado(area);
}
public static double leerValor(String mensaje) {
Scanner scanner = new Scanner(System.in);
System.out.print(mensaje);
return scanner.nextDouble();
}
public static double calcularArea(double base, double altura) {
return (base * altura) / 2;
}
public static void mostrarResultado(double area) {
System.out.println("El area del triangulo es: " + area);
}
}
POO (Triangulo):
public class Triangulo2 {
double base;
double altura;
public Triangulo2(double base, double altura) {
this.base = base;
this.altura = altura;
}
public double calcularArea() {
return (base * altura) / 2;
}
public void mostrarResultado() {
System.out.println("El area del triangulo es: " + calcularArea());
}
}
// Desde main:
public class Main {
public static void main(String[] args) {
Triangulo2 triangulo = new Triangulo2(5, 2);
System.out.println("Base = " + triangulo.base + " y altura = " + triangulo.altura);
triangulo.mostrarResultado();
}
}
Subpaginas del tema: Clases y objetos, Constructores, Visibilidad, Encapsulamiento (getters/setters).
5.2. El metodo toString() y uso de static en la POO
En Java, al trabajar con clases y objetos, dos conceptos fundamentales son los componentes estaticos (static) y el metodo toString().
Subpaginas: El metodo toString(), Metodos y atributos estaticos (static).
5.3. Relaciones simples entre clases
Normalmente, las clases no estan aisladas, sino que se relacionan entre si. Por ejemplo, una clase Alumno y una clase Asignatura: si las dejamos sueltas, no tendremos forma de saber que asignaturas tiene cada alumno.
Para ello, deberemos relacionar las clases entre si.
Subpaginas: Asociaciones/agregaciones, Asociaciones bidireccionales, Paso por valor y por referencia, Composiciones, Asociaciones reflexivas.
BONUS: ArrayList de objetos
Un ArrayList permite almacenar elementos en memoria de manera dinamica. La principal diferencia con los arrays es que el numero de elementos no esta limitado por un numero fijado al inicio.
ArrayList<nombreTipoClase> nombreDeLista = new ArrayList<>();
// Ejemplo:
ArrayList<String> listaPaises = new ArrayList<>();
Metodos tipicos de los ArrayList
add
ArrayList<String> listaPaises = new ArrayList<>();
listaPaises.add("Espana"); // posicion 0
listaPaises.add("Francia"); // posicion 1
listaPaises.add("Portugal"); // posicion 2
// Insertar en posicion especifica:
listaPaises.add(1, "Italia");
// Resultado: Espana, Italia, Francia, Portugal
get
System.out.println(listaPaises.get(3)); // Portugal
remove
listaPaises.remove(2); // elimina por indice
listaPaises.remove("Portugal"); // elimina por objeto
indexOf
int pos = listaPaises.indexOf("Francia");
// devuelve -1 si no lo encuentra
Recorrer con for y foreach
// Con for:
for (int i = 0; i < listaPaises.size(); i++) {
System.out.println(listaPaises.get(i));
}
// Con foreach:
for (String pais : listaPaises) {
System.out.println(pais);
}
Otros metodos
set(posicion, nuevoValor): modifica un elemento.clear(): borra todo el contenido.clone(): retorna una copia de la lista.contains(Object o): retorna true si se encuentra el elemento.isEmpty(): retorna true si la lista esta vacia.lastIndexOf(Object o): devuelve el indice de la ultima ocurrencia.addAll(Arrays.asList(e1, e2, e3,...)): anadir mas de un elemento.
Ejemplo con varargs:
public static void agregarVariosPaises(ArrayList<String> lista, String... paises) {
lista.addAll(Arrays.asList(paises));
}
// Uso:
agregarVariosPaises(listaPaises, "Polonia", "Suiza", "Alemania");
UD6 - 6.1. Herencia y polimorfismo
La herencia en Java es un mecanismo que permite que una clase (subclase o clase hija) adquiera los atributos y metodos de otra clase (superclase o clase padre). Nos ayuda a reutilizar codigo, evitar duplicaciones y crear una jerarquia organizada.
Uso de extends y super
La herencia se implementa con extends, y para acceder a los metodos/atributos de la superclase usamos super.
Ejemplo: Sistema de un Festival
1) Superclase:
class Persona {
String nombre;
int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
public void mostrarInfo() {
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad);
}
}
2) Subclase con extends:
class Asistente extends Persona {
private String entrada;
public Asistente(String nombre, int edad, String entrada) {
super(nombre, edad); // llama al constructor de Persona
this.entrada = entrada;
}
@Override
public void mostrarInfo() {
super.mostrarInfo(); // reutiliza el metodo de Persona
System.out.println("Tipo de entrada: " + entrada);
}
}
3) Otra subclase:
class Artista extends Persona {
String generoMusical;
public Artista(String nombre, int edad, String generoMusical) {
super(nombre, edad);
this.generoMusical = generoMusical;
}
@Override
public void mostrarInfo() {
super.mostrarInfo();
System.out.println("Genero Musical: " + generoMusical);
}
}
4) Main:
public class Concierto {
public static void main(String[] args) {
Asistente a1 = new Asistente("Carlos", 25, "VIP");
Artista art1 = new Artista("Dua Lipa", 28, "Pop");
a1.mostrarInfo();
// Nombre: Carlos, Edad: 25, Tipo de entrada: VIP
art1.mostrarInfo();
// Nombre: Dua Lipa, Edad: 28, Genero Musical: Pop
}
}
Resumen de herencia:
- Persona es la superclase con informacion comun.
- Asistente, Artista son subclases que extienden Persona.
- Cada subclase anade caracteristicas especificas.
- Se usa
super(...)para reutilizar codigo de la clase padre.
6.3. Clases abstractas (abstract + @Override)
Una clase abstracta es una clase que no puede instanciarse por si sola. Sirve como plantilla para que otras clases la hereden y completen su comportamiento.
Los metodos abstractos se crean vacios (sin cuerpo), y las subclases estan obligadas a sobrescribirlos. Se terminan con punto y coma:
public abstract void accederEvento();
Ejemplo:
abstract class Persona {
String nombre;
int edad;
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
// metodo tradicional (con implementacion)
public void mostrarInfo() {
System.out.println("Nombre: " + nombre);
System.out.println("Edad: " + edad);
}
// metodo abstracto (sin implementacion)
public abstract void accederEvento();
}
Si una subclase no implementa el metodo abstracto, Java dara error.
Cuando usar una clase abstracta?
- Cuando tenemos un comportamiento comun que compartir entre varias clases.
- Cuando queremos forzar a las subclases a implementar ciertos metodos.
6.4. Interfaces
Las interfaces son un "contrato" que las clases que las implementen deben cumplir. Una interfaz define que tiene que hacer una clase, pero no el como.
public interface Organizable {
void organizarEvento();
}
- Los metodos son implicitamente publicos y abstractos SIN indicarlo.
- No puede tener constructores.
- Solo puede tener constantes estaticas y finales (
public static final).
Se implementa con implements:
class Organizador extends Persona implements Organizable {
private String rol;
public Organizador(String nombre, int edad, String rol) {
super(nombre, edad);
this.rol = rol;
}
public void mostrarInfo() {
super.mostrarInfo();
System.out.println("Rol en el festival: " + rol);
}
public void accederEvento() {
System.out.println("Accediendo como Organizador.");
}
public void organizarEvento() {
System.out.println("Organizando...");
}
}
Herencia multiple
No se permite heredar de varias superclases, pero si se pueden implementar varias interfaces:
class Organizador extends Persona implements Organizable, Promocionable {
// ...
}
Clases abstractas vs Interfaces
| Clases abstractas | Interfaces |
|---|---|
| Compartir comportamiento comun entre clases relacionadas | Definir un "contrato" que multiples clases deben cumplir |
| Metodos abstractos y tradicionales | Metodos abstractos, default y estaticos (desde Java 8) |
| Puede tener atributos | Solo constantes (public static final) |
| Puede tener constructores | No puede tener constructores |
| Herencia simple (una sola superclase) | Herencia multiple (varias interfaces) |
| Cualquier modificador de acceso | Metodos implicitamente publicos |
Cuando usar cual?
- Clases abstractas: relacion fuerte entre clases, se comparten atributos y codigo. Ej: Animal → Perro, Gato.
- Interfaces: herencia multiple, comportamientos para clases no relacionadas. Ej: Volador → Pajaro, Avion, Superheroe.
6.5. Excepciones personalizadas
Uso de throw para lanzar excepciones manualmente
int edad = 10;
if (edad < 18) {
throw new IllegalArgumentException("Debes ser mayor de edad.");
}
Creacion de Excepciones personalizadas
Clase nueva que herede de Exception o RuntimeException:
public class MiExcepcion extends Exception {
public MiExcepcion(String mensaje) {
super(mensaje);
}
}
Lanzarla con throws en la cabecera:
public static void main(String[] args) throws MiExcepcion {
int valor = -15;
if (valor < 0) {
throw new MiExcepcion("El valor no puede ser negativo.");
}
}
Tambien se puede crear con mensaje fijo:
public class MiExcepcion extends Exception {
public MiExcepcion() {
super("El valor no puede ser negativo.");
}
}
Checked vs Unchecked Exceptions
- Checked Exception (hereda de
Exception): obliga a usarthrowso try-catch. Errores que sabemos que pueden pasar pero no cuando (BD caida, etc.). - Unchecked Exception (hereda de
RuntimeException): no requierethrows. Errores en tiempo de ejecucion provocados normalmente por el usuario.
Exception → debemos usar throws. Si extendemos RuntimeException → no necesitamos throws.
BONUS: Tipos enumerados (enum)
Las clases enum definen tipos de datos que solo pueden tener un conjunto especifico de valores (dias de la semana, estados de un pedido, etc.).
public enum DiaSemana {
LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO;
}
Uso:
DiasSemana diaActual = DiasSemana.JUEVES;
System.out.println("Hoy es " + diaActual); // Hoy es JUEVES
// Recorrer todos los valores:
for (DiasSemana dia : DiasSemana.values()) {
System.out.print(dia + " ");
}
Metodos y constructores en enum
public enum Mes {
ENERO(31), FEBRERO(28), MARZO(31), ABRIL(30),
MAYO(31), JUNIO(30), JULIO(31), AGOSTO(31),
SEPTIEMBRE(30), OCTUBRE(31), NOVIEMBRE(30), DICIEMBRE(31);
private final int dias;
Mes(int dias) {
this.dias = dias;
}
public int getDias() {
return dias;
}
}
// Uso:
Mes mesActual = Mes.ENERO;
System.out.println(mesActual + " tiene " + mesActual.getDias() + " dias");
// ENERO tiene 31 dias
De consola a enum
Usar valueOf(String cadena):
System.out.println("Dime un mes " + Arrays.toString(Mes.values()));
String mes = teclado.next();
Mes mes_enum = Mes.valueOf(mes);
System.out.println(mes_enum + " tiene " + mes_enum.getDias() + " dias");
IllegalArgumentException. Ademas, se diferencia entre mayusculas y minusculas.
Otros metodos utiles
ordinal(): devuelve la posicion (indice) de la constante.name(): devuelve el nombre de la constante como String.
UD7 - 7.1. Colecciones (Collection<E>)
En Java, las colecciones se agrupan usando el Java Collections Framework (JCF):
- Collection<E>: base de las colecciones. Subinterfaces:
- List: permite duplicados (ArrayList).
- Set: elementos unicos, no permite duplicados (HashSet).
- Queue: FIFO (primero en entrar, primero en salir).
- Map<K, V>: pares clave-valor, claves unicas (HashMap, TreeMap, LinkedHashMap).
Metodos tipicos de Collection
boolean add(Object o): anade un elemento.boolean remove(Object o): elimina una instancia del objeto.void clear(): elimina todos los elementos.boolean contains(Object o): verifica si contiene el objeto.boolean isEmpty(): retorna true si esta vacia.int size(): devuelve el numero de elementos.
7.2. Mapas o diccionarios (HashMap)
Un mapa almacena informacion en forma de pares clave-valor. Es como un "diccionario" donde cada entrada tiene una clave unica y un valor.
HashMap<String, Integer> edades = new HashMap<>();
edades.put("Juan", 25);
edades.put("Ana", 30);
edades.put("Pedro", 28);
System.out.println("Edad de Juan: " + edades.get("Juan")); // 25
Metodos principales de HashMap
put(K, V): inserta un par clave-valor. Si la clave existe, pisa el valor.get(K): obtiene el valor a partir de una clave.remove(K): elimina por clave. Tambienremove(K, V)para eliminar solo si el valor coincide.containsKey(K): verifica si la clave existe.containsValue(V): verifica si existe un valor.keySet(): devuelve lista con las claves.values(): devuelve lista con los valores.getOrDefault(key, defaultValue): obtiene valor o devuelve un valor por defecto si la clave no existe.
HashMap<String, Integer> edades = new HashMap<>();
edades.put("Juan", 25);
edades.put("Ana", 30);
edades.put("Pedro", 28);
System.out.println(edades); // {Juan=25, Ana=30, Pedro=28}
System.out.println(edades.get("Juan")); // 25
System.out.println(edades.get("Carlos")); // null
System.out.println(edades.containsKey("Ana")); // true
System.out.println(edades.containsValue(30)); // true
edades.remove("Pedro");
System.out.println(edades); // {Juan=25, Ana=30}
edades.remove("Ana", 29); // no elimina (valor no coincide)
edades.remove("Ana", 30); // si elimina
for (String clave : edades.keySet()) {
System.out.println("Clave: " + clave);
}
for (Integer valor : edades.values()) {
System.out.println("Valor: " + valor);
}
getOrDefault
HashMap<String, Integer> edades = new HashMap<>();
edades.put("Ana", 9);
edades.put("Luis", 7);
System.out.println(edades.getOrDefault("Ana", 18)); // 9
System.out.println(edades.getOrDefault("Pedro", 18)); // 18
Iteracion con entrySet()
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Clave: " + entry.getKey() + ", Valor: " + entry.getValue());
}
Clave con multiples valores
HashMap<String, List<String>> amigos = new HashMap<>();
amigos.put("Juan", new ArrayList<>());
amigos.get("Juan").add("Pedro");
amigos.get("Juan").add("Ana");
System.out.println(amigos); // {Juan=[Pedro, Ana]}
Uso con archivos JSON
El uso principal de los HashMap actualmente es la manipulacion de archivos JSON (APIs, microservicios, bases de datos NoSQL como MongoDB).
// Ejemplo con Gson:
import com.google.gson.Gson;
class Usuario {
String nombre;
int edad;
boolean activo;
}
String json = "{ \"nombre\": \"Carlos\", \"edad\": 32, \"activo\": true }";
Gson gson = new Gson();
Usuario usuario = gson.fromJson(json, Usuario.class);
System.out.println("Nombre: " + usuario.nombre); // Carlos
7.3. Metodos utiles para colecciones
Java ofrece tres herramientas clave para trabajar con colecciones:
- Iterator: permite recorrer colecciones sin exponer su estructura interna, facilitando la eliminacion segura de elementos durante la iteracion.
- Comparable: define un orden natural dentro de una clase, permitiendo comparar objetos entre si de forma predeterminada.
- Comparator: proporciona una forma flexible de definir multiples criterios de ordenacion sin modificar la clase original.
7.4. Programacion funcional (Lambdas y Streams)
La programacion funcional es un paradigma que trata la computacion como la evaluacion de funciones matematicas y evita cambiar el estado o modificar datos (inmutabilidad), promoviendo codigo mas declarativo, conciso y mantenible.
Desde Java 8 se introduce principalmente a traves de:
- Expresiones Lambda: funciones anonimas que se pueden pasar como argumentos. Se crearon como solucion facil a la implementacion de clases anonimas para interfaces que solamente tengan un metodo abstracto.
- Streams: una forma funcional de procesar colecciones de datos (listas, arrays, etc.).
© Temario de Programacion - CFGS