Java JSE

  1. Introducción
  2. Eclipse
  3. Resolución de problemas
  4. Clases
  5. Objetos
  6. Hola mundo
  7. Comentarios
  8. Variables
  9. Constantes
  10. Operadores
  11. Lectura de datos
  12. Estructuras de control
    1. Estructuras de control: if
    2. Switch
    3. Estructura de control: bucle for
    4. Estructura de control: bucle while
  13. Métodos
  14. Modificador static
  15. Arrays
  16. Ejercicio – juego del ahorcado
  17. Ejercicio - tres en raya
  18. Modificadores de acceso
  19. Constructores
  20. Herencia
  21. Polimorfismo
  22. Sobrecarga y sobreescritura
  23. Abstracción
  24. Interfaces
  25. Clases internas
  26. Garbage Collector
  27. String performance
  28. Encapsulamiento
  29. Contenedores
  30. Genéricos
  31. Ejercicio alta, baja, modificación y consulta en un ArrayList
  32. Ejercicio – juego de la oca
  33. Recorrer un map
  34. Sobreescritura del equals
  35. Sobreescritura del hashcode
  36. Sobreescritura del compareTo
  37. Excepciones
  38. Enumeraciones
  39. Hilos

Introducción

  • Lenguaje de programación orientado a objetos.
  • Creado por la empresa Sun Microsystems, actualmente es propiedad de Oracle.
  • Su base es el lenguaje C++.
  • Diseñado para ser independiente de la plataforma de ejecución.

Denominación

Desde v1.2 hasta v1.5 se denominó “Java 2” y se dividió en:

  • Java 2 Platform Standard Edition (J2SE) . Es la base para todas
  • Java 2 Platform Enterprise Edition (J2EE).
  • Java 2 Platform Mobile/Micro Edition (J2ME).

A partir de la versión 5, quita el 2 de su nombre.

  • Java Platform, Standard Edition, Java SE.
  • Java Platform, Enterprise Edition, Java EE.
  • Java Platform, Mobile Edition, Java ME.

Tipos de aplicaciones Java:

  • Aplicación Standalone (escritorio)
    • Programa ejecutado en local mediante la JVM.
    • No requiere navegador para su ejecución.
  • Applet
    • Integrados dentro de una página Web.
    • El applet y la Web se ejecutan de forma local.
  • Servlet
    • Programa ejecutado en servidor
    • Responde a las peticiones realizadas en la web.

La máquina Virtual Java (JVM)

  1. El código fuente se escribe en un archivo de texto plano con extensión Java.
  2. El código es compilado a archivos .class que contienen bytecodes. En el caso de Eclipse, estos ficheros se almacenan en la carpeta bin del proyecto.
  3. Al ejecutar, la aplicación es interpretada por la JVM, transformando los bytecodes en código nativo para el tipo de procesador.
Java virtual machine jvm

La Máquina Virtual Java es el intérprete Java, es decir, es el mecanismo que reconoce el bytecode. Es propia de cada máquina.

El bytecode es independiente de la plataforma y es la JVM la que lo transforma.

JDK (Java Development Kit)

Llamado SDK hasta la versión 5. Luego, JDK.

El JDK se compone de:

  • Compilador java (ejecutable javac), documentación, bibliotecas, herramientas y ejemplos para el desarrollo.
  • JRE (Java Runtime Enviroment, la máquina virtual). Es lo único que necesitan los clientes para ejecutar sus aplicaciones Java.
Descarga JDK

API de Java:
http://docs.oracle.com/javase/6/docs/api/

Descarga de la API de Java:
http://www.oracle.com/technetwork/java/javase/downloads/index.html#docs

Ejemplos de uso:
http://www.programcreek.com/java-api-examples/?action=index

Certificación:

Consta de 77 preguntas tipo test. Debemos acertar un 65% de las respuestas correctas. La duración del examen es 2h 30′ y tiene un coste de 212€.

Buscador de centros homologados para certificarse

Aplicación con preguntas similares a las de la certificación

Resolución de problemas

Precisaremos capacidad de abstracción.

Abstracción:Capacidad que permite representar las características esenciales (atributos y propiedades) de un objeto sin preocuparse de las restantes características (no esenciales).

Abstracción en programación

Ejemplo: Tenemos un grupo de gatitos que o están jugando o están durmiendo. Queremos pasar este situación al mundo de la programación.

  1. Análisis del problema
    • Conocer los datos de entrada y salida
    • Entender como se realiza la transformación
    • lindo gatito
  2. Construcción del algoritmo
    • Se puede expresar mediante gráficos o pseudocódigo
    • gato
  3. Codificación (programar)
    • Creamos el código fuente
    • Gato.javapublic class Gato{
      	String nombre;
      	int edad;
      
      	public void jugar(){
      		...
      	}
      	public void dormir(){
      		...
      	}
      }
  4. Compilar
    • Traducimos el código fuente a código máquina, que será entendido por el ordenador.

Clases

Plantilla o molde a partir de la cual se construye un objeto. Una clase contiene:

  • atributos: propiedades de los objetos (edad, marca, altura, peso...)
  • métodos: acciones que pueden realizar los objetos (corrar, saltar, traducir, pensar...)
public class Gato{
	String nombre;
	int edad;

	public void jugar(){
		...
	}
	public void dormir(){
		...
	}
}
<modificador> class <NombreClase>{
	// Declaración de atributos
	<modificador> <tipo> <atributo>;
	// Declaración de métodos
	<modificador> <tipo> <método> (<args>){
		... 
	}
}

	

Objetos

Se podría decir que un objeto (o instancia de una clase) es una clase con valores concretos.

Clase:

Clase Java Gato

Objeto;

Clase Java Gato

Nomenclatura

  • Paquetes: estoesunpaquete
  • Clases, interfaces y constructores: EstoEsUnaClase
  • Métodos: estoEsUnMetodo()
  • Variables: estoEsUnaVariable
  • Constante: ESTO_ES_UNA_CONSTANTE

Hola Mundo


public class Programa {
	
	public static void main(String[] args) { // Este método es el punto de inicio de toda aplicación JSE

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

System.out.println();
Nos permite escribir trazas en la consola.

El atajo para ejecutar una aplicación J2SE en eclipse es:
alt + shift + x + j

En eclipse, escribiendo:

  • sysout y pulsando ctrl+espacio se nos escribe automáticamente la línea: System.out.println()
  • main y pulsando ctrl+espacio se nos escribe automáticamente el método public static void main.

Comentarios

public class B_comentarios {
	public static void main(String[] args) {
		//Esto es un comentario de una sola linea
		/*
		 * Y esto un comentario de varias lineas
		 * */
		System.out.println("Hola Mundo2");
	}
}

Output:
Hola Mundo2

Variables

Una variable es un espacio de memoria cuya información puede variar durante la ejecución de la aplicación.

En java las variables son altamente tipadas.

Declaración:int n;
int a,b,c;
<tipo> <identificador>; 
<tipo> <identificador_1>,  <identificador_2>...;
Asignación:numero = 42;
<variable> = <expresión>;

La parte izquierda será una variable

La parte derecha puede ser un literal, una variable, una función o una combinación de todos.

Ejemplos:public class Variables {
	public static void main(String[] args) {
		String cadena = "Soy una cadena";
		int i = 5;
		int j = 3;
		System.out.println(cadena);
		System.out.println(i+" + "+j);
		System.out.println(i+j);
		System.out.println(""+i+j);
	}
}

Output:
Soy una cadena
5 + 3
8
53

Tipos de Variables

Primitivas

  • Sus valores se almacenan en el Stack.
  • int i = 1; Crea una variable en el stack que con el nombre i le pone en binario el valor 1.
  • Sólo puedo acceder al valor de i utilizando el nombre de la variable.

Referencias a objetos.

  • Sus valores se almacenan en el Heap.
  • La variable contiene la dirección en memoria del objeto, y no el propio objeto. Por tanto, es posible modificar un objeto mediante diferentes variables.

Stack y Heap: son dos zonas de memoria diferentes en la JVM.

Ej:
int i = 3;
StringBuffer cadenaModificable = new StringBuffer("Pepe");

tipos de variables

Características de las variables primitivas

TYPE CONTAINS DEFAULT SIZE RANGE
boolean true or false false 1 bit NA
char Unicode character \u0000 16 bits \u0000 to \uFFFF
byte Signed integer 0 8 bits -128 to 127
short Signed integer 0 16 bits -32768 to 32767
int Signed integer 0 32 bits -2147483648 to 2147483647
long Signed integer 0 64 bits -9223372036854775808 to 9223372036854775807
float IEEE 754 floating point 0.0 32 bits ±1.4E-45 to ±3.4028235E+38
double IEEE 754 floating point 0.0 64 bits ±4.9E-324 to ±1.7976931348623157E+308

Problema de conversión de tipos

Al convertir la información de un tipo de dato a otro tipo de dato en el que no cabe dicha información, el valor almacenado se verá modificado.

int int_x = 32767;
int int_y = 32768;
int int_z = 32769;
// El rango de un dato de tipo short llega hasta 32767

short short_x = (short) int_x;
short short_y = (short) int_y;
short short_z = (short) int_z;

System.out.println("1 - Conversión a short del int 32767: " + short_x);
System.out.println("2 - Conversión a short del int 32768: " + short_y);
System.out.println("2 - Conversión a short del int 32769: " + short_z);

byte byte_x = (byte) int_x;
byte byte_y = (byte) int_y;
byte byte_z = (byte) int_z;

System.out.println("3 - Conversión a byte del int 32767: " + byte_x);
System.out.println("4 - Conversión a byte del int 32768: " + byte_y);
System.out.println("5 - Conversión a byte del int 100: " + byte_z);

Output:
1 - Conversión a short de 32767: 32767
2 - Conversión a short de 32768: -32768
2 - Conversión a short de 32769: -32767
3 - Conversión a byte de 32767: -1
4 - Conversión a byte de 32768: 0
5 - Conversión a byte de 100: 1

El rango del tipo de dato short es: -32768 to 32767
El rango del tipo de dato byte es: -128 to 127

Casting

Una primitiva grande puede almacenar la información de una de menor tamaño.

Una primitiva pequeña puede almacenar la información de una de mayor tamaño, pero habrá pérdida de información.

El tipo de la variable apuntadora tiene que ser el mismo o más genérico que el tipo de la variable a la que apunta.

Animal animal = new Gato();

tipos de primitivas

Ejercicios variables

  1. Declarar una variable 'i' de tipo int, una variable 'd' de tipo double y una variable 'c' de tipo char. Asignar un valor a cada una. Mostrar por pantalla el valor de cada variable utilizando tres llamadas a System.out.println(). Tener en cuenta que:
    1. Para dar valor a una variable de tipo double ponemos una "d" al final del valor.
    2. Para dar valor a una variable de tipo char, esta debe ir entre comillas simples.
  2. Intercambiar el contenido de dos variables
    int a = 5;
    int b = 7;
    // aquí hay que poner código
    System.out.println(a); //Debería mostrar 7
    System.out.println(b); //Debería mostrar 5

Constantes

Una constante es un espacio de memoria cuya información no puede variar durante la ejecución del programa.

Van precedidas del modificador final.

Si descomento la línea, obtendré un error.

final double PI = 3.14;
System.out.println(PI);
//PI=5;

Operadores

Operador de concatenación

System.out.println("amor " + "Juan");
System.out.println("amor " + nombre);

Ejercicio Saludo

Que se muestre un mensaje que diga: “Buenas tardes Jose”, dónde Jose será el valor de una variable.

Operadores aritméticos

System.out.println(7+2);
System.out.println(7-2);
System.out.println(7*2);
System.out.println(7/2);
System.out.println(7%2);

Operadores lógicos o booleanos

System.out.println(7>2);
System.out.println(7<2);
System.out.println(7>=2);
System.out.println(7<=2);
System.out.println(7==2);
System.out.println(7!=2);

Lectura de datos

Lectura del texto introducido por teclado.

Scanner lector = new Scanner(System.in);
System.out.println("Introduzca un texto, por favor: ");
String variable = lector.next();	
System.out.println("Lectura utilizando la clase Scanner: " + variable);

Según el tipo de datos que estemos usando, utilizaremos:

lector.nextInt();
nextFloat();
...

Ejercicios con operadores aritméticos

  1. Escribir un programa que calcule el número de segundos que existen en un día. Como todo el mundo sabe, un día tiene 86400 segundos.
  2. Hacer un conversor de euros a dólares. El usuario introduce una cantidad de euros y obtiene su valor en dólares. Supondremos que un euro son dos dólares
  3. El IVA para ciertos artículos es del 21%. Realiza un programa que pida un precio y calcule su precio con IVA.
  4. Leer un número entero por teclado y mostrar por pantalla el doble y el triple. Ver solución
  5. Realiza un programa que recoja el ancho y el alto de un rectángulo y calcule el perímetro y el área.
  6. Hacer un conversor de grados centígrados a grados Fahrenheit. Para ello deberé multiplicar por 9/5 y sumar 32. Como todo el mundo sabe, 20 grados centígrados son 68 grados Farenheit.
  7. Vamos a mandar al usuario la caja de sus sueños. Para ello, debemos solicitarle que introduzca su nombre, el material de la caja que quiere recibir, sus dimensiones, y algún comentario. Debemos concatenar esta información para obtener por consola el siguiente mensaje:
    "[nombre] ha pedido una caja de [material] con unas dimensiones [dimensiones]. [comentario].
    Pablo ha pedido una caja de Madera con unas dimensiones diminutas. Que sea muy bonita

Estructuras de control: if

int dato1 = 3;
int dato2 = 5;
if(dato2 > dato1){
	System.out.println("dato2 es mayor que dato1");
}
Output:
dato2 es mayor que dato1

If - else

int dato1 = 3;
int dato2 = 5;
if(dato1 > dato2){
	System.out.println("dato1 es mayor que dato2");
}else{
	System.out.println("Va a ser que no");
}
Output:
Va a ser que no
If – else if
int dato1 = 3;
int dato2 = 5;
if(dato1 > dato2){
	System.out.println("dato1 es mayor que dato2");
}else if(dato2 == dato1){
	System.out.println("va a ser que no");
}else{
	System.out.println("éxito");
}
Output:
éxito
Ejercicios if
  1. Dada una variable que contiene la nota de un examen que va de 0 a 10...
    • Si es mayor que cinco mostrar el texto "Aprobado".
    • Si es igual a cinco, mostrar el texto "Aprobado por los pelos"
    • Si es menor que cinco, mostrar el texto "Suspenso".
  2. Declara una variable 'i' de tipo entero y asígnale un valor. Mostrar un mensaje indicando si el valor de 'i' es positivo o negativo, si es par o impar, si es múltiplo de 5, si es múltiplo de 10 y si es mayor o menor que 100. Consideraremos el 0 como positivo. Ver solución

If especial – operador ternario

int a = 5;
int b = 3;

if(a>b){System.out.println("a es mayor que b");}
else{System.out.println("b es mayor que a");}
		
String resultado = (a>b)?"a es mayor que b":"b es mayor que a";
System.out.println(resultado);

Ejercicios operador ternario

  1. Declare una variable 'b' de tipo entero y asignarle un valor. Mostrar un mensaje indicando si el valor de 'b' es positivo o negativo. Consideraremos el 0 como positivo. Utiliza el operador condicional (? : ) dentro del println para resolverlo.
  2. Declarar una variable 'i' de tipo entero y asignarle un valor. Muestra un mensaje indicando si 'i' es par o impar. Utiliza el operador condicional ( ? : ) dentro del println para resolverlo.

Comparar dos cadenas de texto

String cadena1 = new String("Hola");
String cadena2 = new String("Hola");
String cadena3 = "Adios";
String cadena4 = "Adios";
if (cadena1.equals(cadena2)){
   System.out.println("Son iguales usando el equals");
}
if (cadena1==cadena2){
   System.out.println("Son iguales usando el ==");
}
if (cadena3==cadena4){
   System.out.println("Son iguales usando el = cuando la cadena es creada en un pool");
}

Output:
Son iguales usando el equals
Son iguales usando el = cuando la cadena es creada en un pool

Dado que para objetos se comparan las direcciones de memoria de los mismos, estas no serán iguales puesto que son objetos distintos que almacenan la información en direcciones de memoria distintas.

Cómo convertir una cadena en un entero

String cadena = "3";
Integer numero = Integer.parseInt(cadena);
System.out.println(numero);

Ejercicio calculadora

Hacer una calculadora que determine la operación a realizar por medio de una estructura de control if.

La calculadora debe hacer uso de los operadores aritméticos +, -, * y /.

Estructura de control: Switch

A partir del JDK 7 puede recibir como parámetro un String, antes, no

char opcion = 'b';
switch(opcion){
	case 'a':
		System.out.println("Solucion1");
	break;
	case 'b':
		System.out.println("Solucion2");
	break;
	default:
		System.out.println("default");
}

Nota: Para evaluar cadenas de texto (String) en vez de letras (char), pondré estos caracteres entre comillas dobles en vez de simples.

Output:
Solucion2

Ejercicio – Hacer una calculadora con switch

Hacer una calculadora que determine la operación a realizar por medio de una estructura de control switch.

La calculadora debe hacer uso de los operadores aritméticos +, -, *, / y

Estructura de control: bucle for

for(int i=0; i<10;i++){
	System.out.println(i);
}

0
1
2
3
4
5
6
7
8
9

Ejercicios bucle for

  1. Imprimir los números pares que hay dentro de los 100 primeros números naturales.

    Nota: No utilizar la estructura de control if para resolver este ejercicio.

  2. Imprimir los números pares que hay dentro de los 100 primeros números naturales

    Utilizaremos el operador % para separar los pares de los impares. El bucle avanzará de 1 en 1.

    Nota: El operador “%” nos da el resto de dividir un número entre otro.
    Ej: 7%2=1

  3. Imprimir los números del 1 al 20.

    • Para números divisibles por 3, imprimir “Fizz”.
    • Para números divisibles por 5, imprimir “Buzz”.
    • Para números divisibles por 3 y 5, imprimir “FizzBuzz”.
    • En cualquier otro caso, imprimir el número.

    Notas:

    if(i>5 && i<7) 		//¿i es mayor que 5 y menor que 7?
    if(i==5 || i== 3) 	//¿i es igual a 5 o igual a 3?
  4. Mostrar la tabla de multiplicar de los números del 1 al 9. Usaremos dos bucles anidados. Ver solución
  5. Adivinar el número secreto. En cada iteración del bucle for, se le preguntará al usuario cuál es el número secreto (un número del 1 al 5). Si al cabo de tres intentos no lo ha acertado, el usuario pierde. Si lo acierta, gana.

    Diagrama de flujo de ejercicio de Javascript para adivinar un número secreto
    Notas:

    Para romper la repetición de un bucle, podemos utilizar el comando break;

  6. Calcular utilizando un bucle for, la enésima potencia de un número. Debemos recoger del usuario la base y el exponente. La 4ª potencia será:
    2 x 2 x 2 x 2 = 16

    Diagrama de flujo de ejercicio de Javas para calcular la cuarta potencia de un número
  7. El factorial de un número entero positivo se define como el producto de todos los números naturales anteriores o iguales a él. Se escribe n!, y se lee "n factorial". (Por definición el factorial de 0 es 1: 0!=1) Por ejemplo, 5! = 5·4·3·2·1 = 120

    Diagrama de flujo de ejercicio java

    Variaciones del ejercicio para los que terminen antes : Este ejercicio se puede resolver de varias formas multiplicando los factoriales de mayor a menor o de menor a mayor. Cuando veamos el bucle while, también podremos utilizar esa estructura de control para resolver este ejercicio.

Estructura de control: bucle while

String quieroJugar = "si";
while(quieroJugar.equals("si")){
	...
	System.out.println("¿Quieres seguir jugando?");
	quieroJugar = scanner.next();
}
int i = 0;
while(i<10){
	System.out.println(i);
	i++;
}

Output:
0
1
2
3
4
5
6
7
8
9

Ejercicios bucle while

  1. Repetir el juego del número secreto, pero ahora después de jugar se le preguntará al usuario si desea seguir jugando. Si no desea seguir jugando la variable booleana "quieroJugar" que evalúa el bucle while valdrá false, con lo cual termina el juego.

  2. El usuario debe introducir su nombre, en el caso de que haya introducido información, se le preguntará si los datos son correctos. Si responde que sí, se le indicará que puede seguir con el exámen. Si responde que no, volveremos a la pregunta inicial. En caso de que al principio de todo no hubiese escrito nada, se mostrará un mensaje con la palabra "error"

    Para recoger un valor vacío ("") cuando el usuario pulsa la tecla enter sin introducirar ningún valor, podemos usar el método scanner.nextLine();

    Diagrama de flujo de ejercicio java
  3. Leer números hasta que el usuario introduzca un -1 y mostrar cuantos fueron introducidos.

Métodos

Son funciones aplicadas a objetos.

Una función es un subprograma que realiza una tarea específica y devuelve un valor

Métodos para trabajo con cadenas

String nombre = "Rodolfo";
System.out.println(nombre.substring(0, 3));
System.out.println(nombre.indexOf('d'));
System.out.println(nombre.toUpperCase());
System.out.println(nombre.toLowerCase());
System.out.println(nombre.length());

Output:
Rod
2
RODOLFO
rodolfo
7

Ejercicio con cadenas de texto

  1. En el texto "Dábale arroz a la zorra el Abad", contar el número de veces que aparece la letra "a". Usaremos una estructura "if" con substring e equals que irá dentro de un bucle for.

Método que no devuelve nada (void), sin parámetros

public class MetodoNoDevuelveNada {

	public void saludar(){
		System.out.println("Buenas tardes!");
	}

	public static void main(String[] args) {
		MetodoNoDevuelveNada metodoNoDevuelveNada;
		metodoNoDevuelveNada = new MetodoNoDevuelveNada();
		metodoNoDevuelveNada.saludar();
		
		//La siguiente línea daría error porque el método no devuelve nada
		//String valorDevuelto = MetodoNoDevuelveNada.saludar();
	}
}

Método que no devuelve nada, con parámetros

public class Main {

	public void saludar(String nombre){
		System.out.println("Buenas tardes " + nombre + "!");
	}
	public static void main(String[] args) {
		Main main = new Main();
		main.saludar("Juan");
		//La siguiente línea daría error porque el método no devuelve nada
		//String valorDevuelto = b_metodoNoDevuelveNada.saludar("Juan");
	}
}

Ejercicios con métodos

Resolveremos los siguientes ejercicios definiendo una función con parámetros de entrada y que siempre devolverá un valor que procesaremos en la clase main,

  1. Crear y llamar a un método que recibe un número y calcula su cubo.
  2. Crear y llamar a un método que recibe la velocidad en Km/hora y la muestre en metros/hora.
  3. Crear y llamar a un método que recibe el ancho y el alto de un rectángulo y calcula su perímetro.
    Perímetro: 2*ancho + 2*alto
    Cuando una función recibe varios parámetros, estos irán separados por coma. Ejemplo:
    calculaPerimetro(int alto, int ancho);
  4. Crear y llamar a un método que recibe la base y la altura de un triángulo y calcula su área.
    Area triángulo = base*altura/2
  5. Utilizando estas dos funciones:
    • calculaPerimetro(int radio);
      Perímetro de la circunferencia = 2*PI*Radio
    • calculaArea(int radio);
      Area de la circunferencia = PI*Radio2
    Leer por teclado el valor del radio de una circunferencia y mostrar por pantalla su perímetro y area.
  6. Repetir el ejercicio de la calculadora utilizando funciones para las operaciones aritméticas. Debemos declarar 4 funciones con dos parámetros de entrada cada una: suma(int a, int b), resta(int a, int b,), multiplicacion(int a, int b), division(int a, int b).

Métodos que devuelven algo

Nativos de Javaint valor = lector.nextInt();

//Esta línea da error porque no podemos almacenar en una variable lo que devuelve el método println, ya que no devuelve nada	
String valor2 = System.out.println("hola");
Desarrollados por nosotrospublic class Main {
	public String saludar(String parametroEntrada){
		String respuesta = "Buenas tardes " + parametroEntrada;
		return respuesta;
	}
	public static void main(String[] args) {
		Main main = new Main();
		String valorDevuelto = main.saludar("Juan");
		System.out.println(valorDevuelto);
	}
}

Ejercicios return

Repetir los 6 ejercicios anteriores, pero añadiendo un return a las funciones y utilizando el valor que estas devuelven.

Ejercicio coste viaje

A lo largo de este ejercicio, deberemos programar 3 funciones:

  • costeHotel()
  • costeAvion().
  • costeAlquilerCoche().
Ejercicio - coste hotel

Además del public static void main que te paso más bajo, crea una función llamada costeHotel() que recibe como parámetro el número de noches. La función debe devolver cuanto es necesario pagar teniendo en cuenta que cada noche cuesta 140 euros.

public static void main (String [] args){
	Scanner scanner = new Scanner(System.in);
	System.out.println("Introduzca el numero de noches: ");
	int numNoches = scanner.nextInt();
	System.out.println("Introduzca el destino: ");
	String destino = scanner.next();
		
	CosteViaje costeViaje = new CosteViaje();
	int resultado1 = costeViaje.costeHotel(numNoches);
	int resultado2 = costeViaje.costeAvion(destino);
	int resultado3 = costeViaje.costeAlquilerCoche(numNoches);

	int sumaCostes = resultado1 + resultado2 + resultado3;
	System.out.println("Coste total: " + sumaCostes);
}

Ejercicio – Coste avión

La función costeAvion() tiene como parámetro de entrada el nombre de la ciudad. El coste corresponderá al indicado en la siguiente tabla.

Los costes por ciudad son los siguientes:
"Oviedo": 15
"Tokyo": 700
"Madrid": 90
"Barcelona": 90

Ejercicio – Coste alquiler coche

La función costeAlquilerCoche() tiene como parámetro de entrada el número de noches y devolverá un coste en función de dicho parámetro, teniendo los siguientes aspectos en cuenta:

  • Cada día de alquiler cuesta 40 €.
  • Si alquilas un coche por 3 días o más, obtienes un descuento de 20€ sobre el total.
  • Si alquilas un coche por 7 días o más, obtienes un descuento de 50€ sobre el total (no acumulable con los 20€ de haber alquilado por más de 3 días).

Colección de ejercicios

Ver colección de ejercicios.

Ver el código fuente de los ejercicios.

Paso de parámetros

  • Por referencia: cuando paso la dirección del dato.
  • Por valor: cuando paso el dato.

En Java se pasa siempre por valor, pero:

  • cuando paso un objeto, paso una dirección de memoria.
  • cuando paso una primitiva, paso directamente su valor.

public class PasoParametros {
    public static void modificaLetra(char letraParam) {
        letraParam = 'A';
    }
    public static void modificaStringBuffer(StringBuffer stringBufferParam) {
        stringBufferParam.append("C");
    }
    public static void modificaLetras(char[] letrasParam) {
        letrasParam[0] = 'A';
    }
    public static void modificaPalabras(String[] palabrasParam) {
        palabrasParam[0] = "B";
    }
    public static void modificaString(String stringParam) {
        stringParam = "B";
    }
    public static void main(String[] args) {
        char letra = 'a';
        StringBuffer stringBuffer = new StringBuffer("c");
        char[] letras = {'a'};
        String[]palabras = {"b"};
        String string = "b";

        modificaLetra(letra);
        modificaStringBuffer(stringBuffer);
        modificaLetras(letras);
        modificaPalabras(palabras);
        modificaString(string);

        System.out.println("La nueva letra vale: " + letra);
        System.out.println("El nuevo stringBuffer vale: " + stringBuffer);
        System.out.println("El nuevo letras vale: " + letras[0]);
        System.out.println("El nuevo palabras vale: " + palabras[0]);
       
        System.out.println("El nuevo String vale: " + string);
    }
}

Output:
La nueva letra vale: a
El nuevo stringBuffer vale: cC
El nuevo letras vale: A
El nuevo palabras vale: B
El nuevo String vale: b

diagrama ejercicio Java diagrama ejercicio Java

Modificador static

Métodos static

No precisan de una instancia para ser invocados.

public class MetodosStatic {
	public static void main(String[] args) {
		ClaseConMetodoStatic.saludar("Pepe");
	}
}

class ClaseConMetodoStatic{
	public static void saludar(String nombre){
		System.out.println("Hola " + nombre);
	}
}

Si llamo a un método static desde una instancia, me da un warning, pero compila.

public class H_notaMetodosStatic {
	public static void saludar(String nombre){
		System.out.println("Hola " + nombre);
	}
	public static void main(String[] args) {
		G_metodosStatic g_metodosStatic = new G_metodosStatic();
		g_metodosStatic.saludar("Pepe");
	}
}

Los métodos static no pueden usar las características no static de una clase.

public class I_notaMetodosStaticII {

	String variableNoEstatica = null;
	public static void saludar(String nombre){
		variableNoEstatica = "Rodolfo";
	}
}

Ejercicio

Rehacer la calculadora, pero convirtiendo sus cuatro métodos en static.

Tipos de variables por su posición

public class VariablesSegunPosicion {
	/*
	* ATRIBUTOS
	* 
	* - Se crean al hacer una instancia de la clase.
	* - Se destruyen cuando el garbage colector destruye dicha instancia. 
	* - Si no las inicializo, se inicializan solas, ya sea con el valor 0 o null según el caso.
	* 
	*/
	
	String variableMiembro;
	
	public void metodo(){
		/*
		*VARIABLES LOCALES
		* 
		* - Se crean cuando se lee su línea en el método. 
		* - Se destruyen cuando termina de leerse dicha variable.
		* - Debo inicializarlas para poder usarlas.
		*
		*/	
		
		String variableAutomatica=null;

		/*
		*VARIABLES DE CLASE
		* 
		* - Usan el modificador static. 
		* - Su valor es compartido por todas las instancias de la clase
		* 
		*/	
		
		String variableAutomatica=null;


	}
}

Variables static

Van precedidas del modificador static.

El modificador static permite acceder a las variables y métodos aunque no tengamos una instancia del objeto que los contiene.

Al contrario que con las variables no static, no habrá una por cada instancia de una clase, sino una para todas las instancias de la clase.

public class VariableStatic {
	static int variableEstatica = 0;
	int variableNoEstatica = 0;
	public static void main(String [] args){
		//Incrementamos la variable estática
		VariableStatic.variableEstatica++;
		VariableStatic.variableEstatica++;

		//Incrementamos la variable no estática
		VariableStatic  variableStatic_1 = new VariableStatic();
		VariableStatic  variableStatic_2 = new VariableStatic();

		variableStatic_1.variableNoEstatica++;
		variableStatic_2.variableNoEstatica++;

		//Mostramos resultados
		
		System.out.println(VariableStatic.variableEstatica);
		System.out.println(variableStatic_1.variableNoEstatica);
		System.out.println(variableStatic_2.variableNoEstatica);
		
	}
}

Arrays

Un array es una colección de datos ordenados del mismo tipo y de longitud fija. Una vez que adjudicamos un tamaño al array, no será posible modificarlo. Hay otro tipo de datos llamado "colecciones" cuya longitud si podré modificar.

public class H_Arrays {
	public static void main(String[] args) {
		String [] dias1 = {"lunes", "martes", "miercoles", "jueves", "viernes", "sabado", "domingo"};
		String [] dias2 = new String[7];
		dias2[0]="lunes";
		dias2[1]="martes";
		dias2[2]="miercoles";
		dias2[3]="jueves";
		dias2[4]="viernes";
		dias2[5]="sábado";
		dias2[6]="domingo";

		System.out.println(dias1[5]);
		System.out.println(dias2[3]);		
	}
}

Al instanciar un array debe quedar definida la longitud del mismo.

Ejercicios arrays

  1. Crea un array con cinco nombres de persona y recórrelo con un bucle for mostrando el texto "Conozco a alguien llamado ". Tener en cuenta que la propiedad .length de un array me devuelve el número de elmentos que contiene.
  2. Recorrer la siguiente lista con un bucle imprimiendo el doble de cada número:
    myList = {1,9,3,8,5,7}
  3. El usuario debe introducir un número correspondiente a cierto mes (un valor entre 1 y 12) y la aplicación debe mostrar el nombre del mes correspondiente a dicho número.
    Para resolver el ejercicio utilizaremos un array de strings dónde cada una de las posiciones del array será cada uno de los meses del año.

Números aleatorios

Generar número aleatorio dentro de un rango

Math.floor(Math.random() * (MAX - MIN + 1)) + MIN;

Como ejemplo, si deseamos generar un número aleatorio entre 25 y 75, la sentencia sería

Math.floor(Math.random() * (75-25+1)) + 25;

Ejercicio número aleatorio

Obtener un número aleatorio entre 5 y 7.

Ver ejemplo

Ejercicio letra aleatoria

Obtener una letra aleatoria de tu nombre.

Tendrás que usar los siguientes códigos:

Math.floor(Math.random() * (MAX - MIN + 1)) + MIN;
nombre.chatAt(int posicion);
nombre.length();

Pasos para resolver el ejercicio:

  1. Calculamos un número aleatorio en un rango comprendido entre 0 y el número de letras de tu nombre.
  2. Utilizamos ese número aleatorio para obtener la letra de tu nombre que ocupa esa posición.
  3. Mostramos la letra en la consola.
Ver ejemplo

Ejercicio – juego del ahorcado

Realizar el juego del ahorcado.

  • Al iniciar el programa, este deberá escoger una palabra al azar entre 3. Esta será la palabra secreta que el usuario deberá adivinar.
  • el programa contará la cantidad de letras de la palabra escogida y creará ese número de letras (inicialmente con guiones bajos).
  • El usuario deberá ir introduciendo letra a letra hasta adivinar la palabra secreta.
  • Si el usuario acierta alguna de las letras de la palabra, su correspondiente guión bajo será sustituido por la letra correspondiente.

Será útil tener un array de char en el que iremos guardando cada una de las letras que el usuario vaya acertando.

Notas

Para generar un número aleatorio de tipo double entre 0 y 1:
Math.random()

Si quiero comparar dos arrays, no utilizaré el método equals, sino el método
Arrays.equals(array1, array2)

cadena.indexOf("a") devuelve la primera posición de la letra a en la cadena. En caso de no encontrar coincidencia, nos devolverá -1.

Métodos de la clase String:

charAt(int index)
Devuelve el valor de la letra en la posición especificada.

toCharArray()
Convierte una cadena de texto en un array de letras.

Diagrama de flujo del juego del ahorcado Ejercicio mes seleccionado

Ejercicio - Tres en raya

Hacer el juego del tres en raya para dos jugadores.

  • El tablero será un array de 9 posiciones.
  • El método showTablero(String [] tablero); imprimirá el tablero, con las fichas “X” y “O” dónde correspondan.
  • El juego tendrá un método juegaPlayer(String ficha, String [] tablero) que recibirá como parámetro de entrada el identificador del jugador ("X" o "O") y dónde se le solicitará al jugador el número correspondiente a la posición dónde quiere mover su ficha ("X" o "O").
  • Después de cada tirada, se comprobará si alguien ha ganado, llamando al método evaluateWin(String [] tablero);
  • El método evaluateWin(String [] tablero) mediante sucesivos if todas las posibilidades de que un jugador gane.
  • Utilizaremos la consola de java para ver el tablero.

Tres en raya

Ejercicio – tictactoe solo jugador

El método juegaPlayer1() quedará así:


function juegaPlayer1(){
	board[getComputerMove()] = "X";
}

Por tanto, debemos crear un método getComputerMove() que creará una copia del tablero, moverá la ficha sobre esta copia y en función del resultado decidirá dónde mover en el tablero real. Para realizar el movimiento:

  1. Comprobamos si la máquina puede hacer un movimiento y ganar la partida. (utilizaremos el método evaluateWin, creado en el ejercicio anterior).
  2. Comprobamos si tras mover, el humano puede hacer un movimiento y ganar la partida.
  3. Comprueba si hay alguna esquina libre. Si lo está, la máquina mueve a la esquina.
  4. Comprueba si el centro está libre. Si lo está, la máquina mueve al centro
  5. Mueve a cualquiera de las casillas restantes.
Ejercicio mes seleccionado

Modificadores de acceso

Determinan la accesibilidad de una clase, sus variables miembro y métodos.

Pueden ser:

  • private

    Un elemento private sólo es accesible dentro de la propia clase en que es declarada.

  • default (package, o friendly)

    Es el modo de acceso cuando no hay modifier.
    Se puede aplicar a una clase o a sus datos y metodos. Un elemento default sólo es accesible desde clases pertenecientes al mismo package.

  • Protected (proteje a los hijos)
    Es menos restrictivo que default
    Un elemento protected sólo es accesible desde clases pertenecientes al mismo package y desde las subclases de la clase que tiene el modificador.

    com.pablomonteserin.a.A.javapublic class A{
    	protected void metodoDeA() {
    		System.out.println("hola");
    	}
    }
    com.pablomonteserin.b.B.javapublic class B extends A{
    	void metodoDeB() {
    		B b = new B();
    		b.metodoDeA();
    	}
    }
  • public
    Es el modifier "mas generoso"
    Un elemento public puede accederse sin restricciones.
Modificador La propia clase Mismo paquete Clase hija
mismo paquete
Clase hija
(dif. paquete)
Desde cualquier clase
public X X X X
protected X X X
sin modificador (package) X X
private X

Constructores

Constructor vacío

Generar constructor automáticamente:
Btn dch → Source → Generate Constructor using Fields

public class Main {
	public static void main(String[] args) {
		new MiClase();
	}
}
class MiClase{
	public MiClase(){
		System.out.println("Hola!!!");
	}
}

Output:
Hola!!!

Constructor con parámetros

public class Main {
	public static void main(String[] args) {
		new Persona("Pepe");
		//La siguiente línea genera un error en tiempo de compilación
		//	new Persona();
	}
}

class Persona{
	public Persona(String nombre){
		System.out.println("Hola" + nombre);
	}
}

¿Para qué sirve un constructor?

public class Main {
	public static void main(String[] args) {
		new Persona("Pepe");
	}
}

class Persona{
	String nombre;
	public Persona(String nombre){
		this.nombre = nombre;
	}
}

Si no sé cuales son los parámetros que tengo que poner, puedo ponerme sobre el paréntesis de los parámetros y pulsar ctrl + space

Herencia

public class Main{
	public static void main(String[] args) {
		Hijo h = new Hijo();
		h.saludar();
	}
}

class Padre{
	public void saludar(){
		System.out.println("Hola");
	}
}

class Hijo extends Padre{
	
}

Es el mecanismo mediante el cual una clase deriva de otra, de manera que extiende su funcionalidad.

Constructores y herencia

public class Main{
	public static void main(String[] args) {
		new Hijo();
	}
}

class Hijo extends Padre{
	public Hijo() {
		System.out.println("Se ejecuta el hijo");
	}
}

class Padre{
	public Padre(){
		System.out.println("Se ejecuta el padre");
	}	
}

Output:
Se ejecuta el padre
Se ejecuta el hijo

¿Qué se verá por pantalla al ejecutar el siguiente código?

public class Main{
	public static void main(String[] args) {
		Hijo a = new Hijo("hola");
		a.mostrarMensaje();
	}
}

class Hijo extends Padre{
	Hijo(String mensaje){
		super(mensaje);
	}
}

class Padre{
	String mensaje;
	public Padre(String mensaje){

	}	

	void mostrarMensaje(){
		System.out.println(mensaje);
	}
}

Todas las clases que heredan, tienen un constructor que comienza por el método super();. Si yo no escribo este método, el compilador de java lo escribe automáticamente llamando al constructor vacío del padre. Esto puede dar problemas de compilación si el padre no tiene un constructor vacío.

public class MainConError{
	public static void main(String[] args) {
		new Hijo();
	}
}

class Hijo() extends Padre{
	public Hijo(){
		System.out.println("Se ejecuta el hijo");
	}
}

class Padre{
	public Padre(String parametro){
		System.out.println("Se ejecuta el padre");
	}	
}

Ejercicio

Desarrolle una aplicación que contenga las siguientes clases:

Diagrama del ejercio

El constructor de la clase Persona debe incrementar en una unidad la variable numInstancias.

Desarrolle una clase con el nombre Main, que contenga un método public static void main(String args[]), que al ejecutarse cree dos instancias de empleado y dos de cliente e imprima el total de instancias de persona creadas.

Ejercicio - animales

Implemente las clases del siguiente diagrama.

El método compararSonido() comprueba si el sonido que fue pasado en el constructor es más largo que el sonido almacenado en la variable static "sonidoMasLargo". Si es así, sobrescribe el valor de la variable con el nuevo valor.

Desarrolle una clase con el nombre Main, que contenga un método public static void main(String args[]), que al ejecutarse genere una instancia de Vaca, Perro y Gato pasándoles en el constructor el sonido que deben emitir ("muuuuuu", "guauguau", "miau", por ejemplo). El método "compararSonido" debe ser llamado desde dentro del constructor de la clase Animal.

Luego, imprimir el valor de la variable estática sonidoMasLargo.

Diagrama del ejercicio

Polimorfismo

Es la capacidad de un objeto de adoptar diferentes formas.

Una variable referencia es polimórfica cuando su tipo de declaración no coincide con el tipo del objeto al que referencia.

En Java, esto se consigue gracias a la herencia y las interfaces.

Figura f1 = new Figura("Estandar", "Rojo");

Figura f2 = new Circulo("Verde", 5.0);

Sobrecarga y sobreescritura

public class Main{
	public static void main(String[] args) {
		Hijo.sobreEscritura("Se bienvenido ", "Pablo");
		Hijo.sobreCarga( 3 );
	}
}
class Papa{
	public static void sobreEscritura(String txtBienvenida, String nombre){
		System.out.println(txtBienvenida + nombre);
	}
	public static void sobreCarga(String txtDespedida, String nombre){
		System.out.println(txtDespedida + nombre);
	}
}
class Hijo extends Papa {
	//El tipo devuelto no debe cambiar.
	//los parámetros de entrada no deben cambiar
	//La accesibilidad no será más restrictiva que la del método original.
	//Si el método original es static, el método que hace el override, tambień debe serlo.
	public static void sobreEscritura(String txtBienvenida, String nombre){
		System.out.println(txtBienvenida + "......" + nombre);
	}
	//El tipo de dato devuelto puede cambiar.
	//Los parámetros de entrada deben cambiar.
	//La accesibilidad puede ser más restrictiva que la del método original.
	//El número de checked expections arrojadas debe ser el mismo.
	protected static int sobreCarga(int codigoDespedida){
		System.out.println(codigoDespedida);
		return 4;
	}
	
}

Abstracción

Métodos abstractos

Tenemos una clase "Cosa" que tiene un método "botar". Las clases "Ladrillo" y "Pelota" heredan de "Cosa".

La clase Cosa tiene un método abstracto llamado "botar". Es abstracto porque dependiendo de si instanciamos un ladrillo o una pelota habrá que sobrescribir el código para que bote de una forma determinada.

Un método abstracto es aquel que sólo tiene una declaración y no dispone de cuerpo. Está pensado para ser sobrescrito, luego no es posible utilizar el modificador private con un método abstracto.

pelota baloncesto
ladrillo

Se declaran de la siguiente forma (quitando las llaves del cuerpo de la función y poneindo un ; en su lugar):

public void botar();

Clases abstractas

  • Si una clase tiene al menos un método abstracto, dicha clase debe ser abstracta.
  • Una clase que herede de una clase abstracta debe implementar todos sus métodos abstractos (no es necesario sobrescribir los métodos no abstractos,)
  • Abstracto viene a significar "sin definir".

Ejercicio – animales con clase abstracta

Implemente las clases del siguiente diagrama.

El constructor de la clase Animal llamará a los métodos "establecerSonido()" y "compararSonido()"

El método compararSonido() comprueba si la variable "sonido" es más larga que el sonido almacenado en la variable static "sonidoMasLargo". Si es así, sobrescribe el valor de la variable con el nuevo valor.

El método establecerSonido() debe ser sobreescrito para asignar el valor del sonido que toque a la variable sonido.

Desarrolle una clase con el nombre Main, que contenga un método public static void main(String args[]), que al ejecutarse genere una instancia de cada clase de animal.

Luego, imprimir el valor de la variable estática sonidoMasLargo.

diagrama del ejercicio

Interfaces

Una interface es una clase abstracta en la que todos los métodos serán métodos abstractos.

Una clase que implemente una interface también deberá implementar todos sus métodos. Si no deseamos que esto ocurra, podemos añadir a la interface el modificador abstract.

Una variable estática definida dentro de una interfaz será implícitamente definida como constante.

Una interfaz está pensada para ser sobrescrita, luego no tiene sentido utilizar modificadores privados dentro de una interfaz y no están permitidos.

interface Instrument{
	void play();//automáticamente público
}

public class Clase implements Instrument{
	public static void main(String[] args) {
		Clase c = new Clase();
		c.play();
	}
	public void play() {
		System.out.println("Jugar al baloncesto");
	}
}

Ejercicio – animales con interfaz

Implemente las clases del siguiente diagrama.

El método compararSonido() comprueba si el sonido que fue pasado en el constructor es más largo que el sonido almacenado en la variable static "sonidoMasLargo". Si es así, sobrescribe el valor de la variable con el nuevo valor.

Desarrolle una clase con el nombre Main, que contenga un método public static void main(String args[]), que al ejecutarse genere una instancia de cada clase de animal e invoque a sus métodos “compararSonido”.

Luego, deberé imprimir en pantalla el sonido más largo realizado, almacenado en la variable estática sonidoMasLargo.

Diagrama ejercicio

Clases internas

Son aquellas cuya definición está dentro de otra. Una clase interna puede sobrescribir métodos de la clase que la contiene.

public class ClaseExterna {
	private String nombreExt = "Juan";
	class ClaseInterna{
		private String nombreInt = "Pepe";
		public void mostrarNombre(){
			System.out.println(nombreInt);
			System.out.println(nombreExt);
		}
	}
	public void mostrarNombre(){
		System.out.println("Juan");
	}
	public static void main(String[] args) {
		//Puedo acceder a los variables miembro privadas de la clase interna, pero no de la externa
		ClaseExterna claseExterna = new ClaseExterna();
		ClaseInterna claseInterna = claseExterna.new ClaseInterna();
		//Puedo acceder a los metodos de la clase externa
		claseInterna.mostrarNombre();
	}
}

Output:
Pepe
Juan

Clase interna anónima (anonimous inner class)

Se sobrescribe uno o varios métodos de la clase que se está instanciando en una sola operación.

public class C_claseInternaAnonima {
	public static void main(String args[]){
		Comida2 comida = new Comida2() {
			public void comer() {
				System.out.println("Método \"comer\" de la clase Comida2 sobrescrito");
			}
		};
		comida.comer();
	}
}
class Comida2 {
	public void comer() {
		System.out.println("Comiendo");
	}
}

Garbage Collector

Se encarga de liberar memoria asignada a objetos que ya no se utilizan.

El método finalize() define que va a ocurrir cuando un objeto sea recogido por el garbage collector. No suele utilizarse, ya que no podemos determinar cuando se va a ejecutar.

System.gc() sugiere al Garbage Collector que se ejecute, sin embargo esto no asegura que se libere la memoria ocupada por los objetos creados.

public class J_finalize  {
	protected void finalize() {
		System.out.println("Removed");
	}
	public static void main(String[] args) {
		J_finalize finalizeObject = new J_finalize();
		finalizeObject = null;
		System.gc();
	   }
 }

Cuando se que voy a dejar de utilizar un objeto lo igualo a null para que sea elegible por el recolector de basura.

String Performance

Los Strings no se pueden modificar, luego cualquier operación que se haga sobre un String creará uno nuevo.

Por tanto, trabajar con Strings tiene un costo considerable de Performance. Para modificar cadenas de texto usaremos StringBuffer, o a partir de la versión 4.0 de java: StringBuilder.

No se deben concatenar Strings dentro de un bucle. Esto tendría un gran gasto de Performance.

Diferencia performance entre String, StringBuffer y StringBuilder

public class StringPerformance {

	public static void main(String[] args) {
		String s1 = "hola";
		StringBuffer s2 = new StringBuffer("hola");
		StringBuilder s3 = new StringBuilder("hola");
		
		long empieza = System.currentTimeMillis();
		for(int i = 0; i<10000; i++){
			s1 += "hola";
		}
		long final1 = System.currentTimeMillis()-empieza;
		empieza = System.currentTimeMillis();
		for(int i = 0; i < 10000; i++){
			s2.append("hola");
		}
		long final2 = System.currentTimeMillis()-empieza;
		empieza = System.currentTimeMillis();
		for(int i = 0; i <10000; i++){
			s3.append("hola");
		}
		
		long final3 = System.currentTimeMillis() - empieza;
		
		System.out.println("Usando la clase String: " + final1); // output: 220
		System.out.println("Usando la clase StringBuffer: " + final2); // output: 1
		System.out.println("Usando la clase StringBuilder: " + final3); // output: 0
	}
}

Encapsulamiento

La encapsulación impide la manipulación externa de algunas partes de los objetos.

  • Fuerza al usuario a utilizar un método para acceder a los datos.
  • Hace que el código sea más fácil de mantener

Esto mejora la estabilidad de la aplicación, ya que el cambio directo de una variable puede afectar al correcto funcionamiento de la misma.

Esto permite un código más reutilizable, ya que este se encuentra encapsulado en módulos independientes.

Para ello declararé los métodos como public y las variables como private.

Ejemplo
Al hacer un pedido, no pueden venir más de 10 paquetes. Por tanto si vienen menos de 10 paquetes el número de paquetes en el almacén se modifica, pero pero si vienen más de 10 saltaría un mensaje de error.

public class E_encapsulamiento {
	public static void main(String[] args){
		Almacen almacen = new Almacen();
		almacen.setNumCajasAlmacen(12);
		System.out.println(almacen.getNumCajasAlmacen());
	}	
}
class Almacen{	
	private int numCajasAlmacen;

	public int getNumCajasAlmacen() {
		return numCajasAlmacen;
	}
	public void setNumCajasAlmacen(int numCajasAlmacen) {
		if(numCajasAlmacen>10){
			System.out.println("No se admiten entregas de más de 10 unidades");
		}else{
			this.numCajasAlmacen = numCajasAlmacen;
		}
	}
}

Generar automáticamente los get y set:
Btn dch → source → Generate Getters and Setters
Alt + shift + s → Generate Getters and Setters

POJO (Plain Old Java Object)

  • Debe tener al menos un constructor sin argumentos
  • Los atributos de la clase deben ser privados
  • Dichos atributos deben ser accesibles mediante métodos get y set
  • Debe ser serializable

Bean

Es un POJO que además implementa la interfaz serializable.

public class Alumno implements Serializable {
	private String nombre;
	private int edad;

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public int getEdad() {
		return edad;
	}

	public void setEdad(int edad) {
		this.edad = edad;
	}

}

Contenedores

contenedores

Recorrer una colección - Iterator

ArrayList arrayList = new ArrayList();
arrayList.add("vaca");
arrayList.add("perro");
arrayList.add("elefante");

Iterator it = arrayList.iterator();
while(it.hasNext()){
	String cadena =(String) it.next();
	System.out.println(cadena);
}

Genéricos

Fueron introducidos en Java 5.0.

Los tipos genéricos se encuentran incluidos dentro de los caracteres <>. Especifican el tipo de datos que va a almacenar, utilizar o devolver una clase o método.

Genéricos en colecciones

ArrayList <String>arrayList = new ArrayList<String>();
arrayList.add("vaca");
arrayList.add("perro");
arrayList.add("elefante");
Iterator<String> it = arrayList.iterator();
while(it.hasNext()){
	String cadena =it.next();
	System.out.println(cadena);
}

Notas

  • No es necesaria la comprobación de tipos en tiempo de ejecución con lo cual reduce el uso de casting en el código.
  • El código será menos ambiguo y más fácil de mantener.

Genéricos en clases

public class Generics1 {
	public static void main(String[] args) {
	Calculadora calculadora = new Calculadora();
		calculadora.sumar(4, 5);
	}
}

// El tipo de dato generico E (valdria cualquier nombre de clase inexistente)
// podra ser sustituido dentro de la clase por una clase de
// cualquier tipo, que sera definido al declarar e instanciar la clase
class Calculadora {
	public double sumar(T n1, T n2) {
		if (n1 instanceof Number && n2 instanceof Number) {
			return (Double.parseDouble(n1.toString()) +  Double.parseDouble(n2.toString()));
		}	 
		return 0;
	}

	// Un metodo estatico no puede acceder a un tipo de dato generico
	// Cuando declaro un objeto de la clase calculadora defino el tipo de dato
	// que va a sustituir al generico. Sin embargo un
	// metodo estatico es cargado en memoria antes de declarar la clase que lo
	// contiene y por tanto no sabemos que tipo de dato va a
	// sustituir al generico, ya que este tipo de dato es definido al hacer la
	// declaracion
	//public static void sumar2(E e, E e2) {}
}

Un método estático no puede acceder a un tipo de dato genérico. Cuando declaro un objeto de la clase calculadora defino el tipo de dato que va a sustituir al genérico. Sin embargo un método estático es cargado en memoria antes de declarar la clase que lo contiene y por tanto no sabemos que tipo de dato va a sustituir al genérico, ya que este tipo de dato es definido al hacer la declaración

El tipo de dato genérico E (valdría cualquier nombre de clase inexistente) podrá ser sustituido dentro de la clase por una clase de cualquier tipo, que será definido al declarar e instanciar la clase

Caso concreto

Si para usar genéricos en una clase usamos una palabra reservada, los objetos declarados con esa palabra reservada no serán lo que corresponda, sino que serán de tipo Object

class Generics<String> {
	String valor=(String) "elefante";
}

Ejercicio alta, baja, modificación y consulta en un ArrayList

La aplicación consta de 2 clases: Main y Alumno.

La clase Alumno tendrá dos propiedades: nombre (String) y edad (int).

Al arrancar la aplicación desde el Main se nos preguntará por la operación que deseamos hacer:

  • Insertar un nuevo alumno. Esta opción nos solicita el nombre y la edad del alumno que queremos insertar.
  • Eliminar un alumno. Esta opción nos solicita el nombre del alumno que deseamos eliminar. Para borrar el alumno, recorreremos los alumnos buscando el que cumple la condición deseada utilizando el iterator y cuando lo encontremos, llamaremos a:
    it.remove();
  • Modificar los datos de un alumno. Esta opción nos solicita primero el nombre de alumno para poder identificarlo y luego su nuevo nombre y edad.
  • Mostrar todos los alumnos almacenados. Esta opción recorre el ArrayList de alumnos e imprime los nombres de cada uno.

Ejercicio – juego de la oca

Hacer el juego de la oca. El juego constará de una clase Main desde la que se ejecuta el juego y un bean ''Jugador'' con los atributos nombre(String), casilla(int), turnosRestantes(int).

Al comenzar la aplicación se nos solicitará indicar el número de jugadores. Crearemos las instancias de los jugadores pertinentes y los introduciremos en un ArrayList de jugadores. Los jugadores irán tirando el dado y avanzando casillas hasta que alguno llegue a la casilla 63.
Tener en cuenta que:

  • las posiciones 5, 9, 14, 18, 23, 27, 32, 36, 41, 45, 50, 54 y 59 corresponden a las ocas. Si el jugador cae en ellas se imprimirá el texto ''De oca a oca y tiro porque me toca'', el jugador avanzará hasta la siguiente casilla de la oca y volverá a tirar. Para programar esto, una buena idea sería tener un HashMap en el que la clave sería la casilla en la que ha caído el jugador y el valor sería la casilla de destino. Luego, llamando al método hashmap.containsKey(posicion) evaluaremos si el jugador ha caído en una casilla con oca, y si es así, recuperaremos la correspondiente casilla de destino.
  • si el jugador cae en la casilla 19, caerá en la pensión y no podrá tirar el dado en los dos siguientes turnos.
  • si el jugador cae en la casilla 3, caerá en el pozo y no podrá tirar el dado en el siguiente turno.
  • si el jugador cae en la casilla 52, caerá en la cárcel y no podrá tirar el dado en los tres siguientes turnos.
  • las casillas 6 y 12 son puentes. Si el jugador cae en ellas se imprimirá el texto ''De puente a puente y tiro porque me lleva la corriente''. En este caso, si cayó en la casilla 6 irá a la 12 y si cayó en la 12 irá a la 6. En ambos casos, volverá a tirar.
  • las casillas 26 y 53 son dados. Si el jugador cae en ellas se imprimirá el texto ''De dado a dado y tiro porque me ha tocado''. En este caso, si cayó en la casilla 26 irá a la 53 y si cayó en la 53 irá a la 26. En ambos casos se volverá a tirar.
  • la casilla 58 es la muerte, el jugador que cae en ella pierde.(aunque en el juego original lo que ocurría era que el jugador debía regresar a la casilla 1).

Recorrer un Map

Transformar un Map en una Collection

Persona persona1 = new Persona("75367834E", "Nombre1");
Persona persona2 = new Persona("68274736E", "Nombre2");
Persona persona3 = new Persona("90497589E", "Nombre3");
		
Map<String, Persona>; map = new HashMap<String, Persona>();
map.put(persona1.getDni(), persona1);
map.put(persona2.getDni(), persona2);
map.put(persona3.getDni(), persona3);
		
Collection <Persona> collection = map.values();
Iterator <Persona&gr; it = collection.iterator();
while(it.hasNext()){
	System.out.println(it.next().getNombre());
}

Recuperar un map a partir de las claves

El método keySet() devuelve un set de claves. Luego, recuperaré los valores del map utilizando las claves que acabo de almacenar en el Set.

Persona p1 = new Persona("11111111E", "Nombre1");
Persona p2 = new Persona("22222222E", "Nombre2");
Persona p3 = new Persona("33333333E", "Nombre3");
		
Map<String, Persona&gr; map = new HashMap();
map.put(p1.getDni(), persona1);
map.put(p2.getDni(), persona2);
map.put(p3.getDni(), persona3);
		
Set <String&gr;claves = map.keySet();
		
Iterator <String&gr; it = claves.iterator();
while(it.hasNext()){
	String clave =  it.next();
	Persona persona = map.get(clave);
	System.out.println(persona.getNombre());
}

Recorrer un Map (2 formas)

//Para cada elemento key del conjunto map.keySet()
for (String key : map.keySet()){
	System.out.println(key + "=> " + map.get(key).getNombre());
}
//1 Entry es un key-value pair
for(Map.Entry<String, Persona>entry:map.entrySet()){
	String key = entry.getKey();
	Persona3 value = entry.getValue();
	System.out.println(key + "=> " + value.getNombre());
	}
}

Sobreescritura del equals

Sobreescritura del equalsclass Punto2D{
	private int x;
	private int y;
	private String nombre;
		
	public Punto2D(int x, int y, String nombre) {
		super();
		this.x = x;
		this.y = y;
		this.nombre=nombre;
	}
	@Override
	public boolean equals(Object o) {	
		if (!(o instanceof Punto2D)) return false;
		Punto2D punto = (Punto2D) o;
		return (x == punto.x) && (y == punto.y);
	}
}

Ejercicio

Hacer una clase llamada Deposito que tiene un nombre, un largo, un ancho y un alto.

Hacer un método equals (y su correspondiente) que devolverá true cuando dos depósitos tengan el mismo volumen (largo*ancho*alto).

Sobreescritura del hashcode

Este método sirve para comparar objetos dentro de una estructura de tipo Hash (HashMap, HashSet, etc.). Su reescritura no es indispensable, pero sí recomendable.

El método hashCode() devuelve un dato de tipo int que identifica al objeto. Dicho entero debería estar en función de valores que determinen cuando un objeto es igual o distinto de otro, en el caso de una finca largo y ancho.

Es más rápido que el método equals. Cuando comparamos dos objetos...

  1. Si no tienen el mismo hashcode, son diferentes.
  2. Si tienen el mismo hashcode, se llamará al método equals.
Sobreescritura del hashcode (JDK 7)public int hashCode() {
        return Objects.hash(x, y);
}

Sobreescritura del compareTo

El método compareTo determina si un objeto es mayor, menor o igual a otro. Devuelve 0 si son iguales, 1 (o un entero positivo cualquiera) si el primero es mayor q el segundo y -1 (o un número negativo cualquiera) si el segundo es mayor que el primero

Finca.java
public class Finca implements Comparable<Finca>{
	...
	public int compareTo(Finca otherFinca){
		double productoThis = this.getAncho()*this.getLargo();
		double productoOther = otherFinca.getAncho()*otherFinca.getLargo();
		
		int valorDevuelto = 0;
		if(productoThis > productoOther)valorDevuelto=1;
		if(productoThis < productoOther)valorDevuelto=-1;
		if(productoThis == productoOther)valorDevuelto=0;
		return valorDevuelto;	
	}
Main.java
Collections.sort(lista); //Para que esto funcione es necesario implementar la interfaz comparable

Iterator<Finca> it = lista.iterator();
while(it.hasNext()){
	Finca c = it.next();
	System.out.println(c.getNombre());
}
Notas
  • Todos los objetos tienen el método equals(), pero no todos tienen el método compareTo().
  • Si sobreescribo el compareTo, debería sobreescribir el equals. Si sobreescribo el equals, debo sobreescribir el hashcode.

Ejercicio – equals y compareTo

  1. Hacer una clase llamada Deposito que tiene un nombre, un largo, un ancho y un alto.
  2. Dentro de la clase Depósito, definir un método equals (y su correspondiente método hashcode) que devolverá true cuando dos depósitos tengan el mismo volumen (largo*ancho*alto).
  3. Crear 5 depósitos.
  4. Añadirlos a una lista.
  5. Ordenar la lista.
  6. Recorrerla con un iterator.
  7. Comprobar que los depósitos están ordenados por su volumen. Para ello tendremos que haber sobreescrito el método compareTo

Excepciones

excepciones
  • En tiempo de compilación (checked exceptions): Heredan de java.lang.Exception. El compilador nos obligará a controlarlas mediante un bloque Try/Catch o un throws.
  • En tiempo de ejecución (unchecked exceptions): Heredan de java.lang.RuntimeException. Es la única Exception que no es comprobada por el compilador. Un error también se produce en tiempo de ejecución, aunque un error no es una excepción.

Excepción con un catch

int i = 2;
int j = 0;
System.out.println("antes");
try{
	System.out.println(i/j);
}catch (ArithmeticException e){
	System.out.println("en catch");
	e.printStackTrace();
}
System.out.println("despues");

Output:
antes
en el try
en el catch
java.lang.ArithmeticException
despues

Excepción con varios catch

int i = 2;
int j = 0;
System.out.println("antes");
try{
	System.out.println("en el try");
	System.out.println(i/j);
}catch (ArithmeticException e){
	System.out.println("en catch1");
	e.printStackTrace();
}catch(Exception e){
	System.out.println("en catch2");
	e.printStackTrace();
}catch(Throwable t){
	System.out.println("en catch3");
	t.printStackTrace();
}
System.out.println("despues");

Output
antes
en el try
en catch1
java.lang.ArithmeticException
despues

El único catch que se aplica es el que recoge la excepción. Después de este, el programa continua ejecutándose a partir del último Catch.

Para que el código compile, los tipos de Error más generales(Throwable) deben estar por debajo de los más particulares(ArithmeticException)

Throw

public class ArrojarExcepcion {
	public static void main(String[] args) {
		ArrojarExcepcion arrojarExcepcion = new ArrojarExcepcion();
			try {
			arrojarExcepcion.lanzarExcepcion();
		} catch (Exception e) {
			System.out.println("excepción lanzada y procesada");
		}
		arrojarExcepcion.procesarExceptionInSitu();
	}
	public void lanzarExcepcion() throws Exception{
		throw new Exception();
	}
	public void procesarExceptionInSitu(){
		try {
			throw new Exception();
		} catch (Exception e) {
			System.out.println("excepción procesada");
		}
	}
}

Consola:
excepción lanzada y procesada
excepción procesada

Finally

El finally se ejecuta siempre, se trate la excepción o no se trate la excepción. La única forma de evitar que se ejecute el finally es ejecutando un System.exit(), que mata la máquina virtual.

Se utiliza siempre en conjunción con un try-catch. No puede haber nada después del catch y antes del finally.

public static void main(String[] args) {
	int i = 2;
	int j = 0;
	System.out.println("antes");
	try{
		System.out.println(i/j);
	}catch (ArithmeticException e){
		System.out.println("en catch1");
		e.printStackTrace();
	}catch(Exception e){
		System.out.println("en catch2");
	}catch(Throwable t){
		System.out.println("en catch3");
	}finally{
		System.out.println("en finally");
	}
	System.out.println("despues");
}

Output:
antes
en catch1
java.lang.ArithmeticException
en finally
despues

Crear una excepción

Asistente para crear excepciones en Java

Implementar Serializable

Serializar es el proceso de convertir el objeto a bytes, para poder enviarlo a través de red, y después reconstruirlo al otra lado de la red.

Para que un objeto sea serializable basta con que implemente la interfaz Serializable.

Notas

La clase Exception implementa la interfaz Serializable.

serialVersionUID

Es posible que cuando envío un objeto serializado a un lado y a otro de la red tenga diferentes versiones del mismo. Si sucede esto, la reconstrucción de la clase en el lado que recibe es imposible.

Para comparar las versiones de las clases utilizamos el atributoprivate static final long serialVersionUID.
Este nos indica el número de versión de la clase. Debemos ir actualizándolo con las modificaciones de la clase que vayamos haciendo.

Si las versiones de dos clases que implementan Serializable no coinciden, obtendremos una InvalidClassException al deserializar.

Ejercicio excepciones

Implementar una clase Main que llame un método getPrecioConIva() e imprima el valor que devuelve.

Este método getPrecioConIva() que recibirá un número y devolverá dicho número multiplicado por 1.16. Si el precio es mayor que 100 arrojará una PrecioDemasiadoAltoException que será capturada en el Main.

Implementar una clase PrecioDemasiadoAltoException y sobreescribir su método printStackTrace() para que imprima el texto "El precio es demasiado alto".

Ejercicio

Implementar una clase Persona, con los siguientes campos:
String dni
String nombre
Date fechaNacimiento

Se considerará que dos instancias de Persona son iguales si tienen el mismo dni.

Implementar una clase Servicios con los siguientes métodos:

  • getPersonaMayor: Recibe un mapa de personas personas y devuelve la persona mayor. Arroja una SinDatosException si el mapa está vacío (isEmpty()).
    Para comparar las fechas de los objetos de la clase Date:
  • El método getTime() me devuelve los milisegundos de la fecha.
  • Los métodos after() y before() me devuelven true o false dependiendo de que fecha fue anterior.
  • getPrimerNombre: Recibe un mapa igual al anterior y devuelve la persona con el primer nombre en orden alfabético. Arroja una SinDatosException si el mapa está vacío. Desde este método llamaremos al método compareTo().

Implementar una clase Main con un método public static void main que crea dos objetos y realiza entre ellos las comparaciones antes indicadas. También debe indicarnos si dichas personas son iguales.

Para convertir una fecha en un objeto de tipo Date: 
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
Date fecha = formatter.parse(stringFecha)

Esto es MM mayúscula, las minúsculas son para minutos

Enumeraciones

Los tipos enumerados sirven para restringir la selección de valores a algunos previamente definidos

enum instrumentos{
   GUITARRA, TROMPETA, BATERIA, BAJO
};
public class Enumerations {
   public static void main (String... args){
      instrumentos in = instrumentos.BATERIA;
      System.out.println(in);
   }
}

Output:
BATERIA

Declarar constructores, métodos y variables dentro de un tipo enumerado:

enum TamanoCafe{
	CHICO(5), MEDIANO(8), GRANDE(10);
	private int onzas;
	//No se puede invocar al constructor directamente, 
	//este se invoca una vez que se crea el tipo enumerado 
	//y es definido por los argumentos utilizados para crearlo.
	TamanoCafe(int onzas){
		this.onzas = onzas;
	}
	public int getOnzas(){
		return this.onzas;
	}
}
public class Cafe {
	public static void main(String... args){
		TamanoCafe tc;
		tc = TamanoCafe.CHICO;
		System.out.println("Tamaño de café: "+TamanoCafe.CHICO);
		System.out.println("Onzas 1(c1): "+TamanoCafe.CHICO.getOnzas());
	}
}

Hilos

Para usarlos puedo extender thread o implementar Runnable

Dos ejemplos de hilos en la vida real

Cuando se venden las entradas del concierto de Madonna. En poco segundos todas las entradas quedan agotadas debido a la gran demanda. El proceso que materializa la compra de la entrada no debe ser realizado por varios usuarios a la vez, ya que se corre el riesgo de que se vendan más entradas de las que realmente hay disponibles. Esa parte del código habrá que sincronizarla.

Varias personas entran en un supermercado en el que sólo hay un dependiente. Aunque cada cliente deambula por el supermercado a su aire, mirando productos, escogiendo los que tienen menos conservantes, etc. a la hora de pagar los clientes deben ponerse en cola e ir siendo atendidos por el vendedor de uno a uno. Esta sería la parte sincronizada de un algoritmo que representase lo que ocurre en el supermercado.

Ejemplo de aplicación sin usar hilos

Aunque creamos 3 "pseudohilos", realmente no lo son, y el código de un hilo no se ejecutará hasta que no haya concluído el "pseudohilo" anterior.

Main.javapublic class Main {
	public static void main(String args[]){
		HiloFalso hilo1 = new HiloFalso();
		HiloFalso hilo2 = new HiloFalso();
		HiloFalso hilo3 = new HiloFalso();
		hilo1.run();
		hilo2.run();
		hilo3.run();	
	}
}
Hilo.javaclass HiloFalso{
	static double variable1 = 100;
	public void run() {
		variable1-=10;	
		Thread.sleep(500);		
		System.out.println("Variable1:" + variable1);
	}
}

Output:
Variable1:90.0
Variable1:80.0
Variable1:70.0

Extender Thread

Cuando en una instancia que herede de thread llamo al método start estoy invocando el método run de la clase en un nuevo hilo.

Si llamase el método run directamente, estaría ejecutando su código pero sin crear un nuevo hilo.

Método Main para las siguientes implementaciones de hilos:public static void main(String args[]){
	Hilo hilo1 = new Hilo();
	Hilo hilo2 = new Hilo();
	Hilo hilo3 = new Hilo();
		
	hilo1.start();
	hilo2.start();
	hilo3.start();
		
}

Puedo asignar prioridad a los hilos usando:
Hilo1.setPriority((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 2);

Sin sincronización

class Hilo extends Thread{
	static double variable1 = 100;
	public void run() {
		try {
			variable1-=10;
			Thread.sleep(500);
			System.out.println("Variable1:" + variable1 );
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

Ponemos los hilos a dormir para asegurarnos de que en algún momento todos los hilos están ejecutando simultáneamente el método run().

Output:
Variable1:70.0
Variable1:70.0
Variable1:70.0

Modificador synchronized

Sólo permite que un hilo pase por cierto código de cada vez.

Cuando un objeto está sincronizado, se dice que dicho objeto es un monitor y tiene una variable lock.

El lock tiene el id de un hilo.

Con sincronización

class Hilo extends Thread{
	static Object mutex = new Object();
	static double variable = 100;
	public void run() {
		//El parámetro del siguiente método es un objeto (un Object, un String, un Integer...) no nulo sobre el cual se va a sincronizar.EL objeto mutex sería una especie de guardia de tráfico que decide quien pasa y quien no
		synchronized(mutex){ 
			try {
				variable-=10;			//90 80 70
				Thread.sleep(500);
				System.out.println("Variable1:" + variable);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

Output:
Variable1:90.0
Variable1:80.0
Variable1:70.0

Implementar Runnable

Me creo una instancia de la clase Thread, pasándole como parámetro un objeto de una clase que implementa la interfaz Runnable.

Método Main de las siguientes implementaciones de hilos:
public class Main {

	public static void main(String args[]){
		Thread hilo1 = new Thread(new Hilo());
		Thread hilo2 = new Thread(new Hilo());
		Thread hilo3 = new Thread(new Hilo());
		
		hilo1.start();
		hilo2.start();
		hilo3.start();		
	}
}

Nota:
¿Por qué existe la interfaz Runnable?
Porque en Java sólo es posible heredar de una clase y por tanto a veces no será posible hereder de la clase Thread.

Sin sincronización

		
class Hilo implements Runnable{
	static double variable = 100;
	public void run() {
		try {
			variable-=10;			//70 70 70
			Thread.sleep(500);
			System.out.println("Variable1:" + variable);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

Output:
Variable1:70.0
Variable1:70.0
Variable1:70.0

Con sincronización

class Hilo implements Runnable{
	
	static Object mutex = new Object();
	static double variable = 100;
	public void run() {
		synchronized(mutex){ //Aquí viene un objeto (un Object, un String, un Integer...) no nulo 	sobre el cual se va a sincronizar. Es posible usar cualquier objeto porque los métodos que hacen de semáforo pertenecen a la clase Object.
		//EL objeto mutex sería una especie de guardia de tráfico que decide quien pasa y quien no
			try {
				variable-=10;		//90 80	70
				Thread.sleep(500);
				System.out.println("Variable1:" + variable);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

Output:
Variable1:90.0
Variable1:80.0
Variable1:70.0

Ejercicio hilos

Tenemos una aplicación a la que se conectan de manera concurrente 2 personas (2 hilos). Cada una de estas personas debe realizar 3 operaciones. Suponemos que cada una de estas operaciones tardará 10 milisegundos en realizarse. Representaremos las 10 tareas que realizará cada una de estas personas con el siguiente código:

for (int i = 0; i < 3; i++) {
System.out.println("operacion " + i);
	Thread.sleep(10);
}

Si no sincronizamos este código veremos el código de cada una de estas dos personas se entremezclará, dando lugar a unas trazas como estas:

operacion 0
operacion 0
operacion 1
operacion 1
operacion 2
operacion 2

Queremos sincronizar el código para que las operaciones de cada persona no se entremezclen

icono de mandar un mail¡Contacta conmigo!
Pablo Monteserín
contacta conmigoPablo Monteserín

Para dudas técnicas sobre los ejercicios de mis cursos es necesario estar suscrito.