T.A.P.
Técnicas Actuales de Programación
Curso: 2023/24
|
|
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
N no lectivo, N fiesta, N estudio, N exámenes, N clase futura, N clase pasada, N clase anulada |
Clases: {aula: 0.20S} {horario: jueves 10:40-11:30, viernes 9:40-11:30}
Laboratorios: {Laboratorio: 0.23} {horario: 15:00-17:30}
Tutorias {con las excepciones que puedan indicarse en GAUR}: Jueves de 8 a 10 y de 13 a 14; y Viernes de 8 a 9 y de 12 a 14
Para cualquier consulta podeéis esribir a german.bordel@ehu.es
Si teneis dificultad con un proyecto, enviadlo como adjunto siguiendo este procedimiento →Enviar proyecto.
SEPTIEMBRE
TEMA 1 - INTRODUCCIÓN
1.1.- ["Algorithms + Data Structures = Programs" Niklaus Wirth] → Java +XML e Ingeniería del software.
1.2- Lenguajes de programación: Origen, situación actual y características deseables (el caso de Java) [pdf] /*Llegamos a la página 5*/
1.2- /*Continuamos en la página 6*/Lenguajes de programación: Origen, situación actual y características deseables (el caso de Java) [pdf]
1.3- Máquina virtual [el concepto (re)nace con Java] [pdf]
1.4- Obtención e instalación del software a utilizar [pdf] /*Nos quedamos a medias en la página 4*/
1.4- /*Continuamos en la página 4*/Obtención e instalación del software a utilizar [pdf]
SOBRE HERRAMIENTAS UML
Como he comentado, no disponemos de buenas herramientas para trabajar con UML. En todo caso se puede encontrar alguna que supuestamente funciona, pero que son extremadamente caras (las usan algunas grandes corporaciones). Quiero dejar claro que me refiero a herramientas para generar código y hacer ciclo cerrado UML->código->UML, no meramente para dibujar diagramas. Para dibujar sí que existen algunas aplicaciones, y cada vez van apareciendo más, sobre todo últimamente con soporte en la web (online). Evidentemente tienen la misma función de ayuda al desarrollo (particularmente en equipo, donde se comparten ideas y realizan presentaciones), pero a lo que me refería era, como os digo, a su integración con el resto de herramientas.
Las herramientas definitivas pudieron ser las de Rational Software, empresa desde la que se creo UML, pero fue comprada en 2008 por IBM que parece no tener mucho interés en su mantenimiento aunque las siga comercializando. En el tweet de al lado vemos a Grady Booch, uno de los padres de UML y director científico de desarrollo de software de IBM, diciendo que usa StarUML, (en combinación con el IDE CLion para C/C++, y no sé si podrá integrarse con Netbeans -hace 10 años sí, pero todo ha cambiado mucho-).
Una herramienta bastante potente que se integra con los diferentes IDEs (no de modo sencillo) es Visual Paradigm. Sin ser excesivamente cara, tampoco es barata.
En este momento me parece particularmente interesante Modelio por ser un desarrollo en código abierto que ha dado lugar a un "store" en el que presumiblemente se iran encontrando utilidades interesantes (al parecer ahora mismo existe una que permite la generación de código Java, aunque es de pago dentro de una versión comercial de Modelio).
TEMA 2 - USO DEL ENTORNO DE DESARROLLO
2.1.- Ejemplo inicial: el programa "Hola Mundo"
"Hola Mundo" y primera aproximación a los conceptos Clase y Objeto [pdf]
/*Hemos visto hasta la página 3*/
2.1.- Ejemplo inicial: el programa "Hola Mundo"
/*Continuamos en la página 4*/"Hola Mundo" y primera aproximación a los conceptos Clase y Objeto [pdf]
aproximación "mecánica" a los conceptos de Clase y Objeto [9 min.]
2.2.- Bibliotecas de clases: su estructura
Bibliotecas de clases [pdf]
Biblioteca de clases [10 min.]
Biblioteca de clases desde la versión 9 -módulos- [5 min.]
2.3.- Compilación y ejecución (... y desensamblado, decompilación y ofuscación)
Ejecutables para compilación, etc. [pdf]
2.4.- Generación de documentación
OCTUBRE
Adelanto de los puntos 1 y 2 del
TEMA 6 - INTERFACES GRÁFICOS
TEMA 3 - ELEMENTOS BÁSICOS DEL LENGUAJE
Se trata de calcular el número π con precisión de cuatro decimales mediante la serie:
La especificación de cuatro decimales hace referencia al criterio de parada en la suma de términos de
la serie, no a la presentación de la solución, que se hará normalmente mediante System.out.println(.)
y que nos mostrará un número de decimales que no podemos controlar por ahora.
private static double pi() { double piOctavos = 0.0; for (int n = -1; n <= 100_000_000; ) piOctavos += 1.0/(n+=2)/(n+=2); return piOctavos * 8; }
Los programas que se utilizan para calcular PI con un número de decimales extremadamente alto utilizan series evolucionadas a partir de la sorprendente fórmula descubierta por Ramanujan ("El hombre que conocía el infinito") en 1910 y demostrada no hace mucho tiempo:
Su convergencia es exponencial y ya el primer término aporta 6 decimales. Puede ser un buen ejercicio utilizar esta fórmula para el cálculo de PI, aunque enseguida se verá que es "demasiado buena" para lo que podemos hacer con variables primitivas (e incluso con otros recursos que nos aporta Java).
Un video interesante sobre la historia del cálculo de Pi (Veritasium@Youtube)Escriba una RUTINA que determine si un número dado es perfecto o no.
public class Numerologia { public static void main(String[] args) { //TODO programar el ciclo para que "n" recorra los valores requeridos y //de este modo poder llamar a "isPerfect(n)" } static boolean isPerfect(long n) { //TODO hacer lo necesario para que "isPerfect" retorne "true" o "false" si n es perfecto o no. } }
Un número perfecto es un entero positivo igual a la suma de sus divisores propios. Los divisores propios de un entero positivo son todos sus divisores a excepción de sí mismo. (Ejemplo de número perfecto: el 6, porque sus divisores propios son 1, 2 y 3 y 6=1+2+3.)
Utilizando dicha rutina debera obtener una lista de números perfectos entre el 1 y el 100.000
Listado de Números Perfectos [wikipedia]
TEMA 4 - ELEMENTOS RELACIONADOS CON LA ORIENTACIÓN A OBJETOS
Noviembre
Antes de seguir, vemos (1) cómo extender un interface (2) qué sucede con las reescrituras de métodos al ejecutar desde una referencia de clase no concreta
Hacemos un inciso para ver el punto 3 del
TEMA 6 - INTERFACES GRÁFICOS
Antes de volver al tema 4,
- vemos con Netbeans el ejemplo [zip] que se encuentra en la proyección usada ayer,
- y hacemos una primera aproximación a resolver los ejercicios sin orientación a objetos.
Volvemos al
TEMA 4 - ELEMENTOS RELACIONADOS CON LA ORIENTACIÓN A OBJETOS
Sesión de laboratorio ❶ (laboratorio 0.23). Guión: Calculadora [pdf]
Zip con el desarrollo hecho en el laboratorio. Como al parecer os habéis perdido bastante, le daremos las "vueltas" que sean necesarias. Posiblemente la causa es que hemos tropezado con varias cuestiones que están aún por ver: hilos en el arranque del GUI, excepciones, etc. Puede que sea lo más aconsejable volver a este código cada vez que veamos en clase algo de lo que aquí ha aparecido con antelación.
Continuaremos con encapsulamiento abriendo la posibilidad de escribir más de una clase en un fichero, insistiendo en el "protected", hablando del sentido de getters y setters, y sobre reescrituras de métodos.
Sesión de laboratorio ❷ (laboratorio 0.23). Guión: Gráficos en GUIs [pdf]
Veremos en un momento funcionar al recolector de basuras con el código descargable aquí.
y retomamos desde cero las enumeraciones para continuar con lo que nos resta del tema.
TEMA 5 - MECANISMO DE TRATAMIENTO DE EXCEPCIONES Y ERRORES
/* Hemos terminado el tema...
...pero una vez que me habéis hecho ver que estaba cometiendo un error con el tamaño de los arrays en el programa de test del Garbage Collector
he realizado algunas pruebas que cometaré en un pdf que subiré aquí próximamente (↓hecho el día 23↓)*/
Sesión de laboratorio ❸ (laboratorio 0.23). Guión: Una clase típica: Matrix [pdf]
Como indiqué el día 17, dejo aquí un pdf sobre el la "recolección de basuras". El programa descargable que se encuentra en el día 16 nov. ha sido actualizado.
Hemos trabajado sobre la clase Matrix planteada para el último laboratorio. Hemos visto qué son las clases de test
(Del tema 6 quedaba el punto 6.4 pero se corresponde con lo visto en la segunda sesión de laboratorio -día 14 nov.-)
TEMA 7 - EL CONTENIDO DE LA BIBLIOTECA DE CLASES
Ha quedado en el aire en clase el asunto de la necesidad de implementar Cloneable
para poder hacer clones.
Como os decía, Cloneable
es un interfaz vacío que no obliga a escribir ningún método, pero indica a la máquina virtual si debe permitir
la clonación o no.
Si los objetos se pueden clonar con una copia superficial, bastará con indicar que se implementa Cloneable
, y no es necesario reescribir el clone()
En caso contrario (cuando hay objetos internos que habrá que clonar -ojo, que los que sean inmutables no es necesario clonarlos-) reescribiremos el clone().
En clase "no ha funcionado" porque no he debido de escribir el clone()
correctamente en Netbeans (lo he hecho en la pizarra) y no ha requerido arrojar
le excepción CloneNotSupportedException
. Lo aclaramos a continuación:
Comenzamos la clonación con la llamada al super.clone()
, y en ese momento hemos de atender al hecho de que éste puede arrojar
CloneNotSupportedException (lo arrojaremos también ya que no tiene sentido hacer try-catch
: si super.clone()
no funciona no hay salida para hacer un clone "genuino").
Si en esta clase y sus predecesoras se indica que implementan Cloneable
, la excepción no se producirá; si alguna no lo indica, se generará la excepción.
Para mostrar cómo quedaría el clone()
de Matrix
, en la imagen vemos cómo se comienza solicitando un objeto clonado
a la superclase (en este caso sólo Object
), que a nivel de Matrix
será sólo una copia superficial.
Después procedemos a clonar el array bidimensional (asumimos que es nuestro único campo) y dado que el clone()
de los arrays es superficial, al hacerlo sobre "content
" obtenemos un clon del array de apuntadores a filas, pero las filas no se han clonado,
por lo que hemos de recorrerlas clonandolas una a una. (No existe un "deepClone()
" para Arrays puesto que no existe en Object
, donde no tiene sentido.
El clone()
de arrays es superficial dado que no siempre será necesario que sea profundo -por ejemplo en un array de String
-)
Otro asunto que ha quedado en el aire, ha sido que al generar una subclase de Matrix
para ver cómo podemos
reescribir métodos usando la ayuda de Netbeans con la poción "Insert code..." del menú contextual del editor de texto,
he observado que no aparecia la posiblidad de generar equals()
y hashCode()
(entre otros)...
la razón era trivial: no había declarado
ningún campo en la clase y en tal caso no tienen sentido esos métodos.
Sesión de laboratorio ❹ (laboratorio 0.23). Guión: Usando Netbeans para completar una aplicación con varias clases [pdf]
/*Hemos ido resolviendo pácticamente todo, a falta de lo que tiene que ver con las colecciones que veremos en clase a continuación.*/
Diciembre
Durante la primera hora haremos el examen de tipo test en eGela (laboratorio 0.22 al lado del de prácticas)
TEMA 8 - Entrada y salida de datos
TEMA 9 - Hilos
Sesión de laboratorio ❺ (laboratorio 0.23). Guión: La torre de Babel (I/O y Colecciones) [pdf]
//Hacemos todo menos
Corrección de error:findMostSimilars(.)
que lo veremos en clase mañana.
En el cálculo de la similaridad se debe hacer el sumatorio, para todos los caracteres comunes, del mínimo de la frecuencia en cada idioma, pero me despisté y sumé directamente la frecuencia del "segundo" idioma. Por tanto...
donde puse: similarity+=b.freqs.get(c)==null?0:b.freqs.get(c);
debí poner: similarity+=b.freqs.get(c)==null?0:Math.min(a.freqs.get(c),b.freqs.get(c));
//Comenzaremos por ver cómo resolver la función
Añadidos los "Items tenidos en cuenta para obtener la nota del ejercicio final" en el apartado de EvaluaciónfindMostSimilars
de varias formas.
(El código de la práctica se encuentra aquí)
TEMA 10 - Programación en red
Veremos un ejemplo simple de aplicación cliente-servidor. Se tratará simplemente, en primer lugar, de establecer la conexión y enviar Strings desde el cliente al servidor, para, más adelante, completar este ejemplo con la posibilidad de atender a multiples clientes y establecer el la comunicación de Strings en ambos sentidos.
En el siguiente recuadro tenemos el código de un servidor mínimo. En la línea 6 se instancia un objeto ServerSocket
"escuchando" al puerto 6000(puertos), y posteriormente se entra en un ciclo infinito. La primera acción de este ciclo es esperar a un cliente (línea 8). Cuando surje, el método accept
nos devuelve una conexión ("s") con él (un socket
). Al objeto "s" le pedimos los streams de entrada y de salida en las líneas 11 y 12, que después recubrimos convenientemente a nuestras necesidades actuales como un BufferedReader
y un PrintStream
en 13 y 14 (podríamos haber prescindido de todo lo referente a salida puestoq ue este ejemplo no hara uso de ella, pero lo ponemos para mostrar el procedimiento habitual).
Una vez que disponemos de los streams podemos "leer" lo que nos envie el cliente e igualmente "escribirle". En este ejemplo nos limitamos a un bucle de lectura (líneas 17-19) del Reader
sin que exista ninguna particularidad por el hecho de que el origen de los datos sea una máquina diferente. En el ejemplo se consulta si el contenido de la String
recibida es "exit" para terminar el ciclo de lectura. En tal caso se cierra el Socket
(línea 21) terminando el código del ciclo general y volviendo por tanto al punto de espera a un nuevo cliente (línea 8).
Observesé que en este ejemplo la conexión termina por cierre del Socket
por parte del servidor. Lo mismo podría haberse hecho desde el cliente, lo que resultaría en una excepción en la lectura del Reader
. Podía haberse establecido éste como mecanismo de terminación. En todo caso en una aplicación correcta habría de tenerse en cuenta esta posibilidad de cierre de la conexión or parte del cliente, de modo que habrá de atenderse a la excepción que aquí se arroja.
1 import java.io.*; 2 import java.net.*; 3 4 public class Server { 5 public static void main(String[] args)throws IOException{ 6 ServerSocket ss=new ServerSocket(6000); 7 do { 8 Socket s=ss.accept(); 9 System.out.println("\n\n***Conexion establecida con: "+s); 10 11 InputStream is=s.getInputStream(); 12 OutputStream os=s.getOutputStream(); 13 14 BufferedReader br=new BufferedReader(new InputStreamReader(is)); 15 PrintStream ps=new PrintStream(os); 16 String query; 17 while (!(query=br.readLine()).equals("exit")) { 18 System.out.println("He recibido: "+query); 19 } 20 System.out.println("***Cerrando conexion con: "+s+"\n\n"); 21 s.close(); 22 } while (true); 23 } 24 }
El cliente, para establecer una conexión con el servidor conocido, instancia directamente un Socket
indicando la dirección de la máquina del servidor (como texto) y el número de puerto (línea 7). Del mismo modo que en el caso del servidor, el Socket
permite acceder a los streams de entrada y salida que, a continuación, son recubiertos adecuadamente (9-10, 12-13).
Este cliente se limita a leer el teclado línea a línea y enviar la String
al servidor, para lo cual recubre el System.in
con un BufferedReader
y en un ciclo infinito lee de teclado, escribe en el PrintStream
que recubre al stream de salida del Socket
y controla si el texto es "exit" para terminar en tal caso.
1 import java.io.*; 2 import java.net.*; 3 4 public class Client { 5 6 public static void main(String[] args) throws UnknownHostException, IOException { 7 Socket s=new Socket("127.0.0.1",6000); 8 9 InputStream is=s.getInputStream(); 10 OutputStream os=s.getOutputStream(); 11 12 BufferedReader br=new BufferedReader(new InputStreamReader(is)); 13 PrintStream ps=new PrintStream(os); 14 15 BufferedReader teclado=new BufferedReader(new InputStreamReader(System.in)); 16 String q; 17 do { q=teclado.readLine(); 18 ps.println(q); 19 if (q.equals("exit")) System.exit(0); 20 } while (true); 21 } 22 }
Una vez vistos el servidor y el cliente anteriores, vamos a añadir un par de características necesarias para que sean mínimamente de utilidad.
En primer lugar, el servidor sólo es capaz de servir a un cliente, de modo que si un segundo cliente pretende conectar, como el servidor no se encuentra en el accept
, no obtiene respuesta. Para ello es necesario que el servidor tenga capacidad para llevar a cabo un hilo de ejecución asociado a cada cliente, de modo que lo que hacemos es instanciar un nuevo objeto cada vez que se retorna del accept
, dándole el Socket
y delegando en él toda la relación con el cliente.
La clase ThreadedSocket
extiende Thread
y en su método run
se encarga de obtener los streams, recubrirlos adecuadamente y mantener el ciclo de comunicación con el cliente.
1 import java.io.*; 2 import java.net.*; 3 4 public class ThreadedServer { 5 public static void main(String[] args)throws IOException{ 6 ServerSocket ss=new ServerSocket(6000); 7 do { 8 Socket s=ss.accept(); 9 new ThreadedSocket(s); 10 } while (true); 11 } 12 } 13 14 class ThreadedSocket extends Thread { 15 Socket s; 16 ThreadedSocket(Socket s){super(); this.s=s; start();} 17 18 public void run() { 19 System.out.println("\n\n***Conexion establecida con: "+s); 20 21 try{ 22 InputStream is=s.getInputStream(); 23 OutputStream os=s.getOutputStream(); 24 25 BufferedReader br=new BufferedReader(new InputStreamReader(is)); 26 PrintStream ps=new PrintStream(os); 27 ps.println("OK"); 28 String query; 29 while (!(query=br.readLine()).equals("exit")) { 30 System.out.println("He recibido: "+query); 31 ps.println("OK"); 32 } 33 System.out.println("***Cerrando conexion con: "+s+"\n\n"); 34 s.close(); 35 } catch (Exception e) { 36 } 37 } 38 }
En nuestro ejemplo inicial, el cliente sólo era capaz de enviar lineas de texto al servidor, pero normalmente un cliente también tendrá que recibir información, de modo que en este caso también hay tareas a llevar a cabo en paralelo: la que ya llevabamos a cabo -copiar del teclado al servidor- y la nueva -copiar del servidor a la salida estándar-.
Para llevar a cabo la nueva tarea definimos una clase (lo hacemos de modo local a la rutina main
) que se encargará automáticamente de realizar el "eco" de lo recibido del servidor en la salida estándar. De este modo la simple instanciación de un objeto de esta clase nos basta para completar la funcionalidad buscada.
1 import java.io.*; 2 import java.net.*; 3 4 public class ThreadedClient { 5 6 public static void main(String[] args) throws UnknownHostException, IOException { 7 Socket s=new Socket("127.0.0.1",6000); 8 9 InputStream is=s.getInputStream(); 10 final BufferedReader br=new BufferedReader(new InputStreamReader(is)); 11 class Echo extends Thread{ 12 Echo(){setDaemon(true);start();} 13 public void run(){ 14 try {while (true)System.out.println(br.readLine());}catch (IOException e){}} 15 } 16 Echo e=new Echo(); 17 18 PrintStream ps=new PrintStream(s.getOutputStream()); 19 BufferedReader teclado=new BufferedReader(new InputStreamReader(System.in)); 20 String q; 21 do {q=teclado.readLine(); 22 ps.println(q); 23 if (q.equals("exit")) System.exit(0); 24 } while (true); 25 } 26 }
Se ha visto un ejemplo mínimo y se han añadido las dos funcionalidades necesarias para que tengamos la posibilidad de mantener un intercambio de frases entre un conjunto indeterminado de clientes y el servidor. Esto ha sido muy sencillo entre otras cosas porque no ha sido necesario establecer sincronismos entre hilos (no hay cooperación) ni un protocolo de comunicación (sólo se han cruzado cadenas de texto, no mensajes con diferentes contenidos).
Un modo muy sencillo de establecer comunicaciones cliente-servidor mediante un protocolo consiste en intercambiar objetos (en lugar de textos) recubriendo los streams con ObjectInputStream
y ObjectOutputStream
, de modo que la complejidad del protocolo queda embebida en la estructura de los objetos.
---> Servidor y cliente para pruebas (ligeramente retocados frente a lo visto más arriba).
[descomprimir y ejecutar desde la carpeta "classes" como "java edu.upvehu.gbg.clientserver.ThreadedServer
" y "java edu.upvehu.gbg.clientserver.ThreadedClient
"]
Hace algunos años, los alumnos desarrollaron un cliente de "chat". Aquí hay algo de información al respecto.
---> Aquí un Servidor y cliente con intercambio de objetos
He diseñado mi propio tipo de letra. ¿Cómo puedo incluirlo en mi aplicación?
El mundo del diseño de "Fonts" (cosa de las Bellas Artes) y su uso en computadoras no es trivial. Todo lo necesario para el uso con Java se encuentra en el correspondiente tutorial de Oracle. Como respuesta concreta a cómo contar con un tipo de letra propio, se puede entresacar del mismo este sencillo código:
try { GraphicsEnvironment. getLocalGraphicsEnvironment(). registerFont(Font.createFont(Font.TRUETYPE_FONT, new File("A.ttf")); } catch (IOException|FontFormatException e) { /*Handle exception*/ }
donde se añade al entorno gráfico una nueva "font" que, en concreto, es de tipo "trueType" y se lee de un fichero (puede ser de otro tipo y en general leerse de cualquier "Stream" -véase documentación de Font.createFont-)
Quiero hacer un GUI de calculadora que pueda cambiar entre estándar y científica. ¿Cómo lo hago?
La manera más razonable puede ser agrupar en un par de paneles los botones que deben aparecer y desaparecer en cada caso para proceder a quitar y poner dichos paneles cuando se seleccione un modo u otro.
La siguiente imagen muestra una estructura sencilla de ejemplo: el "frame" lleva un "menuBar" donde se encuentran las dos opciones de paso de un modo a otro. Además hay un panel ("contenedorComun") para los botones que no varian entre ambos modos (componentes no visibles en la imagen) y un contenedor para intercambiar botoneras ("contenedorParaIntercambios"). Inicialmente este contenedor lleva dentro el panel de la calculadora estandar, pero se podrá intercambiar con el contenedor de la botonera cientifica que está "aparcado" en la sección "Other Components".
El modo de hacer el intercambio es extremadamente sencillo: basta con asociar a cada opción de menú su acción correspondiente como se ve en la siguiente imagen. No es otra cosa que sacar todo lo que haya en el panel (la botonera actual) y meter la botonera seleccionada. Una vez hecho esto terminamos con un "pack()" que le dice al "layoutManager" que haga su labor (que "empaquete" convenientemente el contenido)
(nota.- en sentido estricto el panel "contenedorParaIntercambios" es innecesario, pero se ha puesto por clarificar el ejemplo.)
Estoy dibujando histogramas en un JPanel... ¿Cómo puedo centrar el texto de una etiqueta?
Para esto hay que manejar la "métrica" de la fuente de texto que se usa en cada momento. La métrica de una fuente tiene que ver con una serie de conceptos medibles.
En el método paint(Graphics g)
se puede acceder a la fuente a través g.getFont()
/ g.setFont()
, y la métrica se obtiene mediante g.getFontMetrix()
.
Es sencillo conocer la anchura de una String con la metrica activa en un momento dado: simplemente basta con usar g.getFontMetrics.stringWidth("Texto a dibujar")
, que nos da el número de pixels que ocupará.
El siguiente ejemplo muestra una String
centrada en un punto (200,100) -con la coordenada Y en su línea base- y unas líneas cruzadas en dicho punto para comprobar que "funciona" correctamente.