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:

Ejemplos de lenguajes de alto nivel:

Ejemplos de lenguajes de bajo nivel:


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:

El bloque de instrucciones esta compuesto a su vez por tres partes:

  1. Entrada de datos: instrucciones que almacenan en la memoria interna datos procedentes de un dispositivo externo.
  2. Proceso o algoritmo: instrucciones que modifican los objetos de entrada y, en ocasiones, creando otros nuevos.
  3. 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:


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:

En resumen: La captura de excepciones nos permite crear programas mucho mas robustos y tolerantes a errores, disponiendo de un algoritmo alternativo para reaccionar a dichas situaciones.

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:


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
}
NOTA: Cuando en una condicion solamente se indica el nombre de la variable, por ejemplo 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");
}
IMPORTANTE: En Java, la sentencia switch sirve para comparar valores estaticos en tiempo de compilacion. Solo podemos usar switch cuando tenemos valores conocidos.

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:

  1. La primera da el valor inicial a una variable de control.
  2. La segunda es la condicion del bucle.
  3. La tercera modifica la variable de control.
for (int i = 0; i < 10; i++) { ... } // 10 pasadas
IMPORTANTE: Si declaramos la variable de control dentro del parentesis, solo podremos usarla dentro del bucle for.

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);
}
aisalida
101020
1326
1632
1938
22
Consejo: Cuando se esta empezando a programar es muy recomendable dedicar tiempo a hacer ejercicios de trazas, porque ayuda a entender mejor las estructuras de control.

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:

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:


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.

El uso de variables globales no es aconsejable por estas razones:

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:

  1. El problema debe tener una salida recursiva.
  2. El problema debe tener una salida no recursiva.
  3. 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:

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

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:


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.

OJO: Aunque no se pueda instanciar directamente, si se puede usar polimorfismo.

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?


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();
}
FIJATE:

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 abstractasInterfaces
Compartir comportamiento comun entre clases relacionadasDefinir un "contrato" que multiples clases deben cumplir
Metodos abstractos y tradicionalesMetodos abstractos, default y estaticos (desde Java 8)
Puede tener atributosSolo constantes (public static final)
Puede tener constructoresNo puede tener constructores
Herencia simple (una sola superclase)Herencia multiple (varias interfaces)
Cualquier modificador de accesoMetodos implicitamente publicos

Cuando usar cual?


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

Regla: Si extendemos 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");
OJO: Si el valor del String no es valido, se producira un IllegalArgumentException. Ademas, se diferencia entre mayusculas y minusculas.

Otros metodos utiles


UD7 - 7.1. Colecciones (Collection<E>)

En Java, las colecciones se agrupan usando el Java Collections Framework (JCF):

Metodos tipicos de Collection


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

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());
}
Nota: Las estructuras HashMap no mantienen el orden de insercion. Si necesitas orden, usa LinkedHashMap.

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:


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:


© Temario de Programacion - CFGS