Java 2
Java 2
las clases anidadas son las que se encuentran dentro de otra clase y pasan a ser miembro cuando se
instancian y pasan a ser atributos de la clase principal:
CONVERSION IMPLICITA Y EXPLICITA:
CONVERCION IMPLICITA:
CONVERCION EXPLICITA:
CASTING:
El buscar es una etiqueta los dos puntos indica que lo siguiente hace parte del bloue de buscar.
Entonces la linea 9 le estamos diciendo que estamos liberando la memoria del objeto
porque al decirle igual a null, es que un objeto siempre debe estar referenciado por una variable en el
momento en que este deja de ser referenciado en este caso cuando es igual a null, se activa el garbage
collector , esto lo que hara liberar la memoria que estaba usando.
ambito de metodo:
ambito de bloque:
CLASES ANONIMAS:
La clase anonima es una clase sin nombre y se define en la misma linea de codigo.
Se usa cuando necesitamos solamenete la
instancia de una clase.
Enum:
también podemos agregarle variables, métodos y constructores. El objetivo principal de enum es definir
nuestros propios tipos de datos (tipos de datos enumerados)
CADENAS: REGIONMATHES:
se usa para comparar regiones de la cadena, ciertas secciones:
CLASE GENERICA:
nos permite utilizar codigo para usarlos con distintos tipos de objetos.
Si usamos
genericos en
lugar de
parametros de
tipo object
tendremos la
ventaja de que
los erros se
comprobaran en
tiempos de
copilacion.
E = elementos
k = claves
N = numeros
T = tipos
V = valores
HERENCIA EN GENERICOS Y WILDCARDS:
yo puedo asignarle a un objeto de un tipo un
objeto de otro tipo, como podemos ver en la
imagen si estos son compatibles.
Y si creo una lista de number puedo agregarle
elementos de tipo number o integer.
Esto se usa con widcards las cuales representan un tipo desconocido y su simbolo es ?, entonces si en
el metodo le cambiamos el number por el ?, el metodo aceptara todo tipo de ArrayList
ahora la solucion no estaria lista porque el for recorre una lista de Number y como nosotros ahora
recibiremos un ArrayList que no sabemos de que tipo es, entonces para solucionar esto le podemos
decir a java que mi widcard sera de una subclase de otra:
con esto le podemos pasar cualquier tipo de arrayList siempre y cuando los objetos que contengan estos
extiendan de number. En las widcars el extends representa tanto el extends de herencia como el
implements de las interfaces.
Este tipo de widcards estan acotadas superiormente es decir aca le estamos diciendo que la clase debe
ser esa o una que extienda de ella.
Ahora hay widcards acotadas inferiormente en donde le decimos que el tipo de argumento es el
indicado o de una superclase del mismo, supongamos que el lista solo acepte Integer Number y Objetc:
EXCEPCIONES:
Se puede poner de esta manera pero comenzar con los mas especificos.
ahora al IOE excepciones como es una sibclase de Exception el segundo catch es inalcanzable porque
siempre se ejecutara el primero, por lo cual abrar un error en donde no se podra compilar.
Lanzar excepcion.
EXPRECIONES LABDA:
Se usan a partir de JAVA8. Se genera un codigo mas compacto y consiso.
Intefaz funcional: es un interfaz que solo tienen un metodo abstracto y solamente uno, este metodo
representa la intencion de la interfaz:
EJ: interfaz Runnable el cual solo tiene un metodo el cual es run. Este es otro ejemplo creado por
nosotros.
Entonces una exprecion labda es un metodo anonimo que no esta ligado a un identificador, las
interfaces funcionales son el objetivo perfecto de las expreciones labda, ya que sino asignamos una
exprecion labda a un objeto de este tipo el compilador no tendra ninguna duda de que nuestro objetivo
es su unico metodo abstractio.
EJ
Entonces aca conseguimos que nuestra exprecion labda se combierta en la implementacion de obtener
valor de la interfaz funcional mi valor.
EJ2:
----------------------------------------------------------------------------------------------------------------------------
NOTA: JAVA YA TIENE INTERFACES GENERICAS
PREDEFINIDAS QUE SATISFACEN CASI TODOS
LOS POSIBLES ESCENARIOS, MIRARLAS ANTES
DE CREAR NUESTRAS PROPIAS INTERFACES.
ESTAS INTERFACES FUNCIONALES PRE-
DEFINIDAS PERTENECEN A JAVA.UTIL.FUNCTION.
----------------------------------------------------------------------------------------------------------------------------
REGLAS Y BUENAS PRACTICAS LABDA:
Este metodo que suma dos enteros ya esta en una clase calculadora:
REFERENCIA A CONSTRUCTORES:
entonces el tipo y la cantidad de parametros del constructor deben concidir con los del metodo de la
interfaz funcional, y este metodo debe devolver un valor de la clase del constructor.
Nota:para conseguir codigo compacto y facil de leer usar referencia a metodos siempre que podamos.
USO LABDA:
el primer uso es la sustitucion de las clases anonimas que muchas veces resultan engorrosas, una de las
clases anonimas mas usadas es la clase Runnable:
veamos como cambia:
----------------------------------------------------------------------------------------------------------------------------
otra cosa que podemos hacer es iterar sobre colecciones usando expreciones labda:
el metodo forEach de la clase iterable tiene como parametro un objeto de tipo consumer que es una
interfaz funcional. El foreach lo que hace es ejecutar el metodo por cada objeto de la coleccion:
----------------------------------------------------------------------------------------------------------------------------
otra utilidad es la de comparacion, hasta java 8 teniamos que implementar una clase que implementara
comparator por cada tipo de comparacion que teniamos que hacer. EJ:
aca lo ordenamos por nombre:
para hacerlo mas compacto todabia podemos usar comparing de la clase comparator que fue
introducido en java8 y que acepta un parametro de tipo fuction, que lo que hace es extraer una clave
comparable y devolver un comparator que compare por esa clave:
es obtienen lo mismo.
----------------------------------------------------------------------------------------------------------------------------
puede simplificarse tambien en el manejo de eventos en entorno grafico:
STREAMS:
en la version 8 de java se introdujo una nueva abstracción para el procesamiento de colecciones los
STREAMS, con ellos java nos provee de una forma de trabajar con colecciones al estilo de la
programación funcional y con esto conseguimos mayor facilidad para desarrollar porgamas
concurrentes libres de errores, con un codigo mas limpio y lejible y una mayor asbtraccion en las
operaciones:
veamos como quedaria un codigo usando STREAMS comparandolo con un codigo clasico:
cliente tiene simplemente tres Strings como atributos, nombre, primer apellido y pais.
Entonces si quisieramos mostrar por pantalla los clientes de la lista cuyos nombres comienzan con M,
entonces si le agregamos condiciones a la lista, el codigo comenzaria a ensuciarse, tendriamos que
agregarle mas condiciones al if, o hacer if anidados empeorando la legibilidad y mantebilidad, veamos
como quedaria con Streams:
los objetos de la clase collection en java tienen un metodo llamado stream, que devuelve un stream con
los objetos de la coleccion:
a su vez los objetos de tipo stream tienen un metodo llamado filter que filtran los objetos que cumplen
un determinado predicado, este predicado acepta expreciones lambda:
y esto nos devolveria otro stream, ahora vamos a llamar a otro metodo del stream llamado forEach que
realiza una acción a cada uno de usus elementos:
con esto obtenemos el mismo resultado que con lo que se hizo arriba.
Nosotros podriamos agregar todos los filtros que quisieramos de manera muy sencilla:
esta funcionalidad de java tiene otra gran ventaja: nos permite de manera muy sencilla paralelizar las
acciones sobre los streams y solo es cambiar el metodo stream() por paralemStream(), cuando un
stream se ejecuta en paralelo java parte los streams es subStreams, los procesa en paralelo y luego
combina los resultados, esto afecta a la velocidad en la que la operacion se procesa, lo cual no siempre
sera mas rapida; valdra la pena usar paralelismo cuando estamos procesando grandes colecciones de
datos en maquinas multihilo:
STREAMS: es una secuencia de elementos que se crea a partir de una fuente de datos como
colecciones, matrices, o recursos de entrada- salida; y que a ese stream se le aplican una serie de
operaciones concatenadas formando un piline que se pueden ejecutar en serie o en paralelo.
OPERACIONES INTERMEDIAS:
cuando trabajamos con streams operando sobre grupos de datos aplicamos una serie de operaciones
sobre los streams, las operaciones que aplicamos sobre un stream que produce otro stream se llaman
operaciones intermedias, estas operaciones no son ejecutadas hasta que no se invoca una operacion
terminal.
Veamos algunas operaciones intermedias mas interesantes que nos ofrece la clase stream:
para este ejemplo tenemos esta clase y la siguiente:
• veamos primero la operacion filter que devuelve un nuevo stream con los elementos que
cumplan con la condicion que indiquemos:
lo primero que haremos es hacer el stream a partir de la fuente de datos en este caso el
arrayList. Y para que se ejecuten las operaciones intermedias necesitaremos una operacion
terminal que produsca un resultado, en este caso usaremos la operacion terminal forEach para
mostrar el resultado:
• operacion peek:devuelve un stream con los elementos del stream y adicionalmente realiza una
accion a cada uno de estos elementos. Adicionalmente esto es muy util cuando estamos
trabajando con piblines muy largos para debuggear o logguear una parte intermedia del proceso;
por ejemoplo en el stream anterior queremos filtrar los clientes que comienzan por M y son de
mexico:
• operacion Map: que transforma los elementos de un Stream utilizando la funcion que le
pasemos(la funcion es un metodo que acepta un argumento y produce un resultado):
• operacion distinct: devuelve un string con los elementos no duplicados segun su metodo
equals:
• Operacion sorted: devuelve un String con los elementos ordenados, para ello los elementos
deben ser comparables:
OPERACIONES TERMINALES:
cuando trabajamos con streams nuestros piblines siempre deben terminar con una operacion terminal
que produce un resultado diferente a un Stream; veamos algunas:
• operacion forEach: realiza una accion a cada elemento del stream, en este caso se mostraran
por pantalla:
es comun que despues de aplicar nuestras operaciones intermedias a los conjuntos de datos, queramos
volverlos a tenerlos en forma de array para eso:
• operacion toArray:
• operacion collect: digamos que queremos guardar el resultado de las operaciones en otro
contenedor tenemos la operacion collect:
• operacion reduce: operacion de reduccion que toma una serie de elementos de entrada y los va
combinando aplicando repetidamente una operacion:
Hay operaciones de reduccion que ya estan implementadas en la clase Stream para usar directamente
por ejemplo:
• operacion allMatch: hay otra operacion que nos dice si todos los elementos cumplen con la
condicion:
• operacion nonMatch: nos dice si ninguno de nuestros elementos cumple con la condicion:
CONCURRENCIA:
LOS HILOS EN JAVA:
Para sacar provecho de los procesadores hoy en dia es necesario el uso de multiples hilos en nuestros
programas, incluso aunque nosotros no lo hagamos de forma explicita algunas de las clases de librerias
de la plataforma java añadiran concurrencia a nuestros programas.
Una aplicacion java siempre tiene al menos un hilo, el hilo principal, y desde este hilo podemos
crear otros hilos.
Se pueden crear tantos hilos como queramos.
Si no le asignamos nombre a los hilos, java lo hara por nosotros, le pondremos nombre nosotros:
la forma que implementa la clase Runnable es mas felxible y mejor porque podemos usar objetos
de cualquier clase siempre que implemente la interfaz runnable.
Entonces veamos el metodo slep que pausa la ejecucion del hilo actual en unos milisegundos, hay un
metodo sobrecargado donde podemos indicarlo en milisegundos y nano segundos, el metodo sleep bota
un error de tipo interruptedException este error sale porque si otro hilo interrumpe a este mientras sleep
este activo:
NOTA: no existe garantia del que el tiempo de sleep sea preciso dependera del sistema donde
corra nuestra aplicacion y lo ocupado que este.
Veamos ahora como podemos hacer para interrumpir un hilo y hacer que haga otra cosa, el hilo que sea
interrumpido debe soportar su interrupcion y proveer las intrucciones que seran ejecutadas en caso de
ser interrumpido, en la mayoria de los casos lo que se hace esterimnar la ejecucion, para esto en este
caso que estamos usando sleep es meter el codigo en el catch:
por otro lado podriamos preguntar si nuestro hilo sigue vivo, este sigue vivo si tienen algo que ejecutar,
y al final de nuestro programa podriamos querer liberar recurso para ellos hariamos:
una manera mas sencilla es usar join al hilo del que se esta esperando.
ERRORES DE LA INTERACCION ENTRE HILOS:
Estos son errores que ocurren cuando usamos concurrencia:
interferencia entre hilos: ocurre cuando se realizan al mismo tiempo dos o mas operaciones sobre un
mismo dato en diferentes hilos intercalandose entre si. El problema ocurre cuando estas operaciones
constan de varias instrucciones y estas se solapan.
errores de consistencia de memoria: se producen cuando dos hilos tienen versiones diferentes de lo
que tendria que hacer el mismo dato.
Vemoas un ejemplo
imaginemos que dos hilos estan actuando sobre un mismo objeto de tipo contador, supongamos que
ambos hilos llaman al metodo incrementar y que aca no sucedera nada extraño que cada uno
incrementarane un la varible, pero no es asi aca el metodo tiene mas de un paso, la maquina virtual de
java la divide en tres pasos:
entonces ca suponemos que los hilos se ejecutaron al mismo tiempo, y aca estamos viendo que estamos
perdiendo una unidad lo cual podria ser catastrofico en lagunos casos.
Estos errores son dificiles de detectar y solo ocurriran aveces.
Cuando trabajamos con variables no volatiles la maquina virtual de java puede optar por copiar
variables de la memoria principal a la cache de la CPU mientras trabaja con ellas para mejorar el
rendimiento, si la maquina contiene mas de una CPU cada hilo podria correr en una diferente y hacer la
copia en su diferente cache, nosotros no tenemos control de cuando la maquina virtual de java copiara
la variable a la cache de la CPU y cuando decide volver a pasarla a la memoria principal, esto hace que
las modificaciones de un hilo no se vean por el otro:
supongamos que partimos con la variable c a 0, si desde el hilo a incrementamos el valor del contador y
despues desde ese mismo hilo lo imprimimos veremos el valor y sera , ahora si lo incementamos en el
hilo a y lo vemos en el hilo b no tenemos garantia de que el valor de contador sea 1:
para segurarnos de que un hilo vea las modificaciones de otro debemos garantizar una relacion
conocida como appendsBefore, veamos que mecanismo nos asegura tener esta relacion:
• cada accion en un hilo ocurre antes que las acciones en ese mismo hilo que bienen despues en el
orden del programa.
• La sincronizacion es el primer mecanismo que nos permite evitar los problemas de interaccion
con hilos, con ella lo que hacemos es bloquear una variable para que solo un hilo pueda
modificarla y desbloquearla cuando la accion ha terminado, el desboqueo ocurre antes que
cualquier bloqueo subsiguiente y en concecuencia cualquier accion de antes del desbloqueo
tendra una relacion de appendsBefore con las que ocurran despues del bloqueo.
• Las variables volatile garantizan que su escritura y lectura sean hechas desde la memoria
principal, la escritura a una variable volatile ocurre antes que cualquier escritura o lectura
subsecuente de la misma varible, por lo tanto el problema anterior se evita haciendo la variable
volatile pero siempre con precaucion porque esto podria afectar el rendimiento del programa, ya
que suele ser mas costoso leer en la memoria pricipal.
• Una llamada start en un hilo ocurre antes que cualquier accion en ese hilo; todas las acciones en
un hilo ocurren antes que una llamada exitosa al metodo join de ese hilo; los metodos de clases
del paquete java.util.concurrent y sus subpaquetes tienen mecanismos que extienden estas
garantias de relacion appendsBefore.
METODOS SINCRONIZADOS:
los problemas de interferencia de hilos y los errores de inconsistencia de memoria producidos al
interactuar mas de un hilo se pueden eliminar mediante la sincronizacion, con la sincronizacion
conseguimos poner orden en como los hilos acceden a los recursos compartidos, una forma de
sincronizacion son los metodos sincronizados, con ellos conseguimos bloquear un objeto para que solo
un hilo pueda usar pueda usar sus metodos sincronizados.
para evitar estos problemas podemos realizar los metodos incrementar y decrementar sincronizados,
para ello solo añadimos la palabra synchronized, para ver los efecto de la sincronizacion vamos a
hacer que el metodo incrementar ademas de incrementar el contador espere 5 segundos:
El desbloqueo del del objeto asegura que todo lo ocurrido antes del desbloqueo ocurre antes de lo
ocurrido despues, y por lo tanto evitamos errores de inconsistencia de memoria.
SENTENCIAS SINCRONIZADAS:
La sincronizacion de metodos es muy util y efectiva a la hora describir una clase segura, pero cuando
usamos clases creadas por otros que no sabemos si son seguras simplemente debemos llamar a los
metodos de estos objetos dentro de un bloque sincronizado.
Veamos un ejemplo:
entonces aca lo que hacemos es sincronizar el bloque de sentencias que realizan llamadas a metodos del
objeto contador, para sincronizar debemos especificar el objeto al cual queremos bloquear y lo
pondremos entre parentesis como se ve arriba. Y se añaden las instrucciones que se quieren sincronizar.
PROBLEMAS DE SINCRONIZACION:
la sincronización no resuelve los problemas que nos trae la interaccion de varios hilos sobre los mismo
recursos, para ello hacemos que un hilo tenga que esperar a otro hilo libere el recurso antes de
continuar su ejecucion, esto trae sus propios problemas, ya que realentiza la ejecucion de los hilos
pudiendo incluso dejandlos bloqueados para siempre.
• Uno de los problemas que mas nos podemos encontrar es el dead lock o punto muerto, en esta
situacion dos o mas hilos estan bloqueados para siempre esperando a que otro haga algo,
veamos un ejemplo:
imaginemos que ana y pablo estan en una conferencia en la que de vez en cuando quieren tomar
notas peros olo hay un cuaderno y un boligrafo.
Entonces pablo hace lo mismo pero el primero toma el boligrafo y luego quiere tomar el cuaderno, al
ejecutarlo sucede esto:
entonces lo que se hace es que cuando los libere ana pablo tendra el cuaderno y el boligrafo.
• Otro problema es el live block en esta situacion un hilo actua en respuesta a la accion o estado
de otro hilo, si esta accion depende a su vez de una accion del primer u otro hilo se produce un
bloqueo que les impide continuar, el ejemplo mas comun es cuando dos personas se encuentran
de frente y no se dejan pasar y se mueven al mismo lado y no se dejan pasar.
OBJETOS INMUTABLES:
La mejor estrategia para asegurarnos de que nuestro codigo es seguro en cuanto a concurrencia es
diseñar nuestros objetos como inmutables, de esta forma nos asegurarnos de que dos hilos no pueden
modificar al mismo tiempo y evitariamos el problema de que un hilo lo modifique sin que otro vea la
modificacion; tendemos a no crear objetos inmutables pensando que estos les reste eficiencia a los
programas, pero este impacto suele estar sobreestimado.
Entonces tenemos la clase circulo que tiene dos variables de instancia, radio y color, en donde color es
una variable de referencia ya que contiene la referencia a un objeto. Y tenemos el constructor y
tenemos los respectivos getters y setters.
Vamos a ver como transformar este objeto a inmutable. Primero es asegurarnos de no tener metodos
que permitan modificar el estado del objeto, el estado del objeto esta representado por sus variables de
instancia, en nuestro caso tenemos tres metodos que lo incumplen, establecerRadio, establecerColor y
escalar a este se adatara para que el metodo devuelva un objeto de tipo circulo como el actual pero
escalado:
lo siguiente es que todas las variables de instancia sean private e final, de tal forma de que no se puedan
modifcar el estado, accediendo directamente a las variables desde el exterior, ademas el al ser final sera
el valor que se mantenga durante toda la ejecucion.
Despues de esto es que no se tenga acceso desde fuera a la referencia de nuestras variables de
referencia que contienen objetos mutables, en este caso la clase color, en esta caso color esta diseñada
como inmutable pero vamos a mirarla como si no lo fuera, entonces en el metodo
estamos devolviendo la referencia del
objeto color, le estamos dando acceso
completo. Entonces vamos a hacer una
copia defenciba que es elo que
devolveremos:
con esto no devolvemos nuestro color sino un color igual al nuestro que sirve como informacion pero
no sirve para transformarlo. Ocurre lo mismo con nuestro constructor, estamos aceptando como color
un objeto que ha sido creado fuera de nuestra clase y por lo tanto tienen acceso total a el
lo otro es que no se pueda extender la clase para esto podemos hacer la clase final,y asi no se
reescribiran los metodos. O podemos hacer el constructor privado y sustituirlos por metodos estaticos
factoria:
LA INTERFAZ LOCK:
La interfaz lock nos permite implementar objetos que puedan ser usados como medio de control de
acceso a recursos compartidos por hilos, es el mismo mecanismo que nos ofrece la sincronizacion pero
mucho mas flexible; ya que por ejemplo nos permite que un hilo se heche atras cuando intenta bloquear
un objeto que esta bloqueado por otro hilo, y puede hacerlo inmediatamente o despues de un tiempo de
espera; tambien nos permite bloquear un objeto de manera que el bloqueo pueda ser interrumpido.
Esta mayor flexibilidad trae con sigo una mayor responsabilidad ya que debemos que asegurarnos de
siempre liberar los recursos.
Lo que hace el metodo imprimir es bloquear el objeto de tipo Lock y simular una impresion que dura
un tiempo aleatorio hasta 10 segundos.
Entonces debemos ver la estructura que se usa comun mente, cuando bloqueamos un objeto de tipo
lock debemos asegurarnos de desbloquearlo bajo cualquier circunstancia y por lo tanto debemos usar
un bloque try y finally, en el finally desbloquearemos el objeto.
Cuando se bloquean varios objetosdebemos librerarlos en orden opuesto.
Vamos a ver que varios hilos compitan por el recurso de una impresora:
otra posibilidad que nos da la interfaz lock es uno de sus metodos que es tryLock, que tiene dos
posibilidades, en la version sin parametros se intentara adquirir el bloqueo solo si esta libre en ese
momento en su version con parametros lo que hace es intentar bloquear el objeto dando un margen de
tiempo de espera para ver si es desbloqueado si es que esta bloqueado en ese momento. Vamos a
probarlo:
veamos que nuestra impresora tiene dos vias de impresion que pueden trabajar concurrententemente
donde una es mas lenta pero de mayor calidad y la otra mas rapida pero de menor calidad; sino
conseguimos acceder a la impresion de calidad hacemos la impresion mas mala:
ahora veamos el tryLock dando el tiempo de espera.
Ahora vemos que todos los hilos que han accedido al recursos antes de 10 segundos desde el inicio de
la ejecucion han hido por la via de calidad el resto por la via rapida.
EJECUTORES:
en java cuando creamos un hilo directamente, lo estamos asociando irremediablemente con la tarea que
debe ejecutar, con los ejecutores lo que conseguimos es dissociar estos dos conceptos consiguiendo un
codigo mucho mas flexible. Java tien una interfaz llamada execute, esta interfaz tienen un unico
metodo pensado para lanzar una tarea especificada por un runnable:
estas añaden varias funcionalidades entre ellas aceptar un callable en vez de un runnable, lo que nos
permite que la tarea devuelva un valor. Tambien nos devuelve un objeto de tipo future que nos permite
comprobar el estado de la tarea y acceder al valor devuelto por el callable, la interfaz
ScheduledExecutorService nos permite programar la tarea para que sea ejecutada en el futuro e incluso
periodicamente.
pero java tiene ya varios implementados que cubren muchos escenarios, podemos acceder a ellos a
travez de la clase executors:
como se puede ver esta clase tiene varios metodos estaticos factory y que nos devuelve diferentes
executers, vamos a ver un ejemplo sencillo con:
aca tenemos un runnable que solo saluda y muestra el nombre del hilo, veamos la forma en que
manualmente creariamos un hilo con la tera y lo lanzariamos:
veamos como hacerlo con un ejecutor:
primero creamos un ejecutor, y llamamos a su metodo submit con la tarea que queremos que ejecute:
se puede usar todas las veces que queramos con la misma o diferentes tareas.
El metodo submit del executor nos devuelve un objeto de tipo future(generico) en nuestro caso sera de
tipo integer, porque nuestro callable devuelve un Intger:
en el momento de llamar a submit no tendremos todavia el resultado ya que es un calculo muy costoso,
y para saber si la tarea se ha completado podemos usar el metodo isDone de future:
si llamamos al metodo getFuture conseguiremos que el hilo actual se bloquee hasta que la tarea se halla
terminado: