sábado, 15 de diciembre de 2012

La clase BufferStrategy

votar
En una entrada anterior te había explicado cómo hacer una pequeña animación en Java. Ahora bien, es posible que te hayas dado cuenta de que no es todo lo perfecta que debiera, si no que se producen pequeños desajustes, o parpadeos, entre las imágenes, o que incluso a veces se pueden ver algo superpuestas.

Esto es así porque el programa está "dibujando" las imágenes a la vez que el sistema está continuamente refrescando la pantalla. Una forma de evitarlo es crear un espacio de almacenamiento donde el programa dibuje la imagen y no a muestre hasta que esté totalmente acabada. Exactamente esto es lo que hace la clase BufferStrategy. Además, se encarga que las imágenes no se muestren más rápido de la velocidad con la que el monitor se refresca, es decir, supongamos que tú tienes un juego en el que aparecen 150 escenas por segundo, pero tu monitor funciona a 75Hz, lo que significa que sólo puede mostrar 75 escenas por segundo, pues la clase BufferStrategy adapta el programa a esta velocidad. 

Lo bueno es que tú sólo tienes que implementarla, ella solita se encarga de manejar los recursos del sistema. Se puede utilizar tanto con Canvas como con Windows, y para crearla elige primero el número de lugares de almacenamiento o "buffers" que deseas y luego utiliza el método createBufferStrategy(), por ejempo así:

ventana.createBufferStrategy(2);

donde 2 es el número de buffers que creamos. Para invocarlo usaremos el método getBufferStrategy(). Por supuesto, hay que dibujar la imagen en el buffer y luego mostrarla. Esto se hace de la siguiente manera:

BufferStrategy buffer = ventana.getBufferStrategy();
Graphics g = buffer.getDrawGraphics();
draw (g);
g.dispose();
buffer.show();

Te dejo a continuación una actualización del código visto en la entrada sobre animación, PantallaCompleta.java, donde ya está implementada la clase BufferStrategy, y ya puestos, se añaden otras mejoras, como el método getCompatibleDisplayModes(), que te ayudará a conseguir el mejor rendimiento posible del sistema que estés utilizando. También te dejo los códigos de AnimaciónTest.java y Animación.java, para que los tengas todos a mano, aunque también los tienes en la entrada correspondiente.
Si aún no sabes crear una pantalla a modo completo, visita esta otra entrada.

PantallaCompleta.java

import java.awt.*;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
import java.awt.image.BufferedImage;

public class PantallaCompleta{
   private GraphicsDevice gd;
   public PantallaCompleta(){
      //hay que usar los medios del propio sistema
      GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
   gd = env.getDefaultScreenDevice();
 }
 
 //devuelve una lista de los modos de visualización compatibles con las características de nuestro sistema
 public DisplayMode[] getCompatibleDisplayModes(){
    return gd.getDisplayModes();
 }
 
 //devuelve el primer modo compatible o nada si no encuentra ninguno
 public DisplayMode findFirstCompatibleMode(DisplayMode modes[]){
    DisplayMode goodModes[] = gd.getDisplayModes();
    for(int i = 0; i < modes.length; i++){
     for(int j = 0; j < goodModes.length; j++){
     if(displayModesMatch(modes[i], goodModes[j])){
      return modes[i];
    }
  }
    }
    return null;
  }
  
  //devuelve el modo de visualización actual
  public DisplayMode getCurrentDisplayMode(){
     return gd.getDisplayMode();
 }
 
 /**
     Determina si dos modos de visualización coinciden, es decir,
  si tienen la misma resolución, velocidad de actualización de
  pantalla, etc.
 */ 
 public boolean displayModesMatch(DisplayMode mode1, DisplayMode mode2){
    if(mode1.getWidth() != mode2.getWidth() || mode1.getHeight() != mode2.getHeight()){
     return false;
    }
    if (mode1.getRefreshRate() !=
        DisplayMode.REFRESH_RATE_UNKNOWN &&
     mode2.getRefreshRate() !=
     DisplayMode.REFRESH_RATE_UNKNOWN &&
     mode1.getRefreshRate() != mode2.getRefreshRate()){
      return false;
  }
  return true;
 }
      
 /**
     Se pone la pantalla a modo pantalla completa y se cambia
  el modo de visualización. En caso de que no hubiese ninguno
  compatible o no se pudiese cambiar, se utiliza el que tiene
  el sistema
 */
 public void setFullScreen(DisplayMode displaymode){
    JFrame ventana = new JFrame();
    ventana.setUndecorated(true);
    ventana.setIgnoreRepaint(true);
    ventana.setResizable(false);
    
    gd.setFullScreenWindow(ventana);
    
    //comprobamos que el sistema soporta el cambio
    if(displaymode != null && gd.isDisplayChangeSupported()){
       try{
      gd.setDisplayMode(displaymode);
    }catch(IllegalArgumentException e){}
  }
  ventana.createBufferStrategy(2);
 }
 
 public Graphics2D getGraphics(){
    Window window = gd.getFullScreenWindow();
    if(window != null){
       BufferStrategy strategy = window.getBufferStrategy();
    return(Graphics2D)strategy.getDrawGraphics();
  }else{
    return null;
  }
 }
 
 public void update(){
    Window window = gd.getFullScreenWindow();
    if(window != null){
       BufferStrategy strategy = window.getBufferStrategy();
    if(!strategy.contentsLost()){
      strategy.show();
    }
  }
  
  //sincronizamos con la ventana del sistema
  Toolkit.getDefaultToolkit().sync();
 }
 
 public Window getFullScreenWindow(){
    return gd.getFullScreenWindow();
 }
 
 public int getWidth(){
    Window window = gd.getFullScreenWindow();
    if(window != null){
       return window.getWidth();
  }else{
    return 0;
  }
 }
 
 public int getHeight(){
    Window window = gd.getFullScreenWindow();
    if(window != null){
     return window.getHeight();
    }else{
     return 0;
    }
 }
 
 //restauramos los valores previos
 public void restoreScreen(){
    Window w = gd.getFullScreenWindow();
    if(w != null){
       w.dispose();
  }
  gd.setFullScreenWindow(null);
 }
 
 /**
    Creamos una imagen compatible con el modo de visualización,
    que se almacena en la memoria del sistema
 */
 public BufferedImage createCompatibleImage(int w, int h, int transparency){
    Window window = gd.getFullScreenWindow();
    if(window != null){
      GraphicsConfiguration gc = window.getGraphicsConfiguration();
   return gc.createCompatibleImage(w, h, transparency);
  }
  return null;
 }
}


Animación.java



import java.awt.Image;
import java.util.ArrayList;

public class Animación{
   private ArrayList frames;
   private int actualFrame;
   private long tiempoAnimación;
   private long tiempoTotal;
   
   public Animación(){
      frames = new ArrayList();
   tiempoTotal = 0;
   start();
 }
 
 public synchronized void addFrame(Image image, long duración){
    tiempoTotal += duración;
    frames.add(new AnimFrame(image, tiempoTotal));
 }
 
 public synchronized void start(){
    tiempoAnimación = 0;
    actualFrame = 0;
 }
 
 public synchronized void update(long tiempoTranscurrido){
    if(frames.size()>1){
     tiempoAnimación += tiempoTranscurrido;
     if(tiempoAnimación >= tiempoTotal){
     tiempoAnimación = tiempoAnimación % tiempoTotal;
     actualFrame = 0;
  }
  while(tiempoAnimación > getFrame(actualFrame).endTime){
     actualFrame++;
  }
    }
 }
 
 public synchronized Image getImage(){
    if (frames.size() ==0){
       return null;
  }else{
     return getFrame(actualFrame).image;
  }
 }
 
 private AnimFrame getFrame(int i){
    return (AnimFrame)frames.get(i);
 }
 
 private class AnimFrame{
    Image image;
    long endTime;
    public AnimFrame(Image image, long endTime){
       this.image = image;
    this.endTime = endTime;
  }
 }
}


AnimaciónTest.java


import java.awt.*;
import javax.swing.ImageIcon;


public class AnimaciónTest {

    public static void main(String args[]) {

       //DisplayMode displayMode= new DisplayMode(1024, 768, 32,
                //DisplayMode.REFRESH_RATE_UNKNOWN);
        AnimaciónTest test = new AnimaciónTest();
        test.run();
    }
    private static final DisplayMode POSSIBLE_MODES[]={
     new DisplayMode(800, 600, 32, 0),
        new DisplayMode(800, 600, 24, 0),
        new DisplayMode(800, 600, 16, 0),
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 24, 0),
        new DisplayMode(640, 480, 16, 0)
    };

    private static final long DEMO_TIME = 10000;

    private PantallaCompleta pc;
    private Image bgImage;
    private Animación animación;

    public void loadImages() {
        // cargamos las imágenes
        bgImage = loadImage("images/background.jpg");
        Image cara1 = loadImage("images/cara1.png");
        Image cara2 = loadImage("images/cara2.png");
        Image cara3 = loadImage("images/cara3.png");

        // creamos la animación
        animación = new Animación();
        animación.addFrame(cara1, 250);
        animación.addFrame(cara2, 150);
        animación.addFrame(cara1, 150);
        animación.addFrame(cara2, 150);
        animación.addFrame(cara3, 200);
        animación.addFrame(cara2, 150);
    }


    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void run() {
        pc = new PantallaCompleta();
        try {
      DisplayMode displayMode = pc.findFirstCompatibleMode(POSSIBLE_MODES);
            pc.setFullScreen(displayMode);
            loadImages();
            animationLoop();
        }
        finally {
             pc.restoreScreen();
        }
    }


    public void animationLoop() {
        long tiempoInicio = System.currentTimeMillis();
        long tiempoActual = tiempoInicio;
        while (tiempoActual - tiempoInicio < DEMO_TIME) {
            long tiempoTranscurrido =
                System.currentTimeMillis() - tiempoActual;
            tiempoActual += tiempoTranscurrido;

            // actualizamos la animación
            animación.update(tiempoTranscurrido);

            // se dibuja en pantalla
            Graphics2D g =
                pc.getGraphics();
            draw(g);
            g.dispose();
   pc.update();

            // una pequeña pausa
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) { }
        }

    }


    public void draw(Graphics g) {
        // se dibuja el fondo
        g.drawImage(bgImage, 0, 0, null);

        // se dibuja la imagen y la centramos más o menos
        g.drawImage(animación.getImage(), 300, 200, null);
    }

}


Si quieres ver más a fondo la clase BufferStrategy, puedes visitar estas páginas:


Vale, están todas en inglés, así que, si no lo dominas, es una suerte que esté yo, ¿eh? ;-)

domingo, 2 de diciembre de 2012

Introducción a los genéricos (generics) en Java

votar
Érase una vez, en una galaxia muy, muy lejana, antes de Java 5 y de la existencia de genéricos, los programadores java que utilizaban colecciones, se enfrentaban a un temible peligro.

Ellos podían, por ejemplo, crear una lista de Strings como la siguiente:

List miLista = new ArrayList( );
miLista.add(“Hola”);
miLista.add(“Galaxia”);
miLista.add(new Integer(2001));

podían, incluso, hacer un casting al obtener los elementos de esa lista:

String s = (String) miLista.get(0);
String st = (String) miLista.get(1);
String str = (String) miLista.get(2);

¡Pero nada de eso evitaba que al ejecutarse el programa se produjera un error! ¿Por qué? ¡Porque el tercer elemento no es un String, sino un Integer, pero nada impedía añadirlo a la lista! Por suerte, los genéricos vinieron al rescate de estos desdichados programadores y nunca más se produjeron errores de este tipo.

Bueno, vale, puede que la historia no fuese exactamente así, pero nos sirve para explicar en qué consisten los genéricos y su función. Desde luego los genéricos no se utilizan sólo con las colecciones, pero es cierto que es en ellas donde te los vas a encontrar mayoritariamente.

Los genéricos permiten asignar parámetros a las clases, interfaces, métodos..., de forma que sólo admitan los tipos de objetos que tu quieras. No se pueden utilizar con primitivos, pero si con las clases que se corresponden con ellos. Por eso no puedes hacer un genérico tipo int, pero sí un Integer. Veamos el ejemplo anterior utilizando genéricos:

List <String> miLista = new ArrayList <String>();
//al poner <String> estamos indicando que la lista sólo acepta Strings
miLista.add(“Hola”); //un String, perfecto
miLista.add(“Galaxia”); //otro String, muy bien
miLista.add(new Integer(2001)); //¡error de compilación, no es un String!

Evidentemente, es mucho más sencillo solucionar un error de compilación que un error de ejecución. Además, no hace falta ningún casting para obtener los elementos de la lista:

String s = miLista.get(0);
String st = miLista.get(1);
//el compilador ya sabe que es un String, no hace falta casting.

Para conseguir esto, basta poner entre los signos < >, llamados diamante, el tipo al que queremos que pertenezcan los elementos: String, Integer o lo que sea. Además, para facilitar más las cosas, a partir de Java 7 la primera línea de este código se escribiría así:

List <String> miLista = new ArrayList<>();

porque el compilador ya infiere de qué tipo son los elementos de la lista.

Pero, ¿cómo crear nuestra propia clase genérica? Fíjate en este ejemplo:

public class Cajón<T>{
   private T t;
   public void set (T t){
   this.t = t;
   }
   public T get( ){
   return t;
   }
}

Ya hemos construído una clase genérica, y le hemos proporcionado dos métodos. La T es el tipo de parámetro que le vamos a asignar. Supón por ejemplo que quieres un Cajón de Calcetines y otro Cajón de Camisetas, pues sustituyes T por Calcetines o por Camisetas (por supuesto, si no existen las clases Camisetas ni Calcetines, debes crearlas) de esta forma:

public class Cajón<Calcetines> o public class Cajón<Camisetas>

Todos los métodos de tu clase genérica pueden ser utilizados en cualquiera de ellas, pero no podrás usar calcetines con Cajón<Camisetas> ni camisetas con Cajón<Calcetines>.

Por convención, los nombres de los tipos de parámetros se escriben como una letra mayúscula, y los más habituales son :
  • T, de tipo
  • E, de elemento, muy usado con la Java Collection Framework
  • N, de número (class Number)
  • K, de key
  • V, de value, estas dos últimas utilizadas sobre todo en mapas (Map)

¿Y cómo sería un método en el que estemos utilizando genéricos?

void añadirCalcetines (Cajón<Calcetines> cal){
   cal.add(new Calcetines( ));
}

Un método muy simplón, pero sirve para que veas la sintaxis. Y hablando de sintaxis, es muy habitual con los genéricos ver esto: <?> acompañado a veces de “extends” o “super”. Ahora mismo te explico lo que significa. Supongamos que tienes una lista de números enteros:

List<Integer> miLista = new List<>( );

y piensas, “bueno, Integer es una subclase de Number, así que puedo hacer esto” y escribes:

List<Number> miLista = new List<Integer>( );

¡No compila! ¿Por qué? ¡Porque List<Integer> no es un subtipo de List<Number>! En List<Number> sólo pueden entrar objetos que pertenezcan a la clase Number, no objetos de la clase Integer, ni de la clase Double, ni Short, ni Long. Sólo de la clase Number, por eso, aunque a la lista puedes añadirle números enteros, o decimales o los que quieras que pertenezcan a Number, no puedes decir que es una nueva lista de números enteros. Ahora bien, si escribimos esto:

List<? extends Number>

estamos indicando que esta lista va a admitir objetos de la clase Number y de cualquier otra que la extienda (“?” significa cualquiera). Así pues, modificamos

List<?extends Number> miLista = new List<Integer>( );

y ahora sí que compila. Y también compilará si en lugar de List<Integer> utilizamos cualquier otra subclase de Number, como List<Double>.

Pero, ¿qué ocurre si lo que queremos es lo contrario, es decir, que la lista admita objetos de la clase Integer y su superclase, pero no ninguna otra subclase de Number?
En ese caso, haríamos lo siguiente:

List<? super Integer>

con lo que la lista admitirá objetos de la clase Integer y de cualquier otra clase superior a ella, como Number.

¿Y qué ocurre si hacemos esto?:

List<?>

Lo que estamos indicando ahora es que la lista admitiría cualquier tipo de objetos. No confundir con “objetos de la clase Object”. Es decir, esto:

List<Object>

sólo admite objetos que pertenezcan a la clase Object, y no podemos hacer lo siguiente:

List<Object> miLista = new List<Calcetines>( ); //no compila

Por último, quiero añadir que es cierto que los genéricos en Java no son perfectos (como los programadores en C siempre señalan amablemente) pero eso es así porque desde un principio se busco que fuese compatible con todo el código escrito con anterioridad a Java 5, que no los utilizaba. No era cuestión de que todo ese código dejase de funcionar, ¿no?

Si has tenido dificultades en seguir esta entrada porque no sabes lo que son las colecciones, puedes ver esta entrada anterior. Si el problema es con el polimorfismo y la herencia, consulta esta otra. Para ampliar conocimientos, consulta los tutoriales de Oracle.

Si tienes dudas, pregúntame. Y si te ha gustado, compártelo.;-)


En respuesta a la consulta de Anet (ver Comentarios)
Hola, Anet:
¡Es un placer ver una chica por aquí!
Respecto a tu pregunta, no tengo muy claro qué es exactamente lo que quieres hacer, así que no sé si la respuesta será la que buscas.
Como ya hemos visto, los genéricos siguen las reglas de herencia de Java a nivel de tipo, pero no a nivel de parámetro.
Por ejemplo, podemos hacer

Set<String> mySet = new HashSet<String>();

porque HashSet es un subtipo de Set, pero no

Set<Object> mySet = new HashSet<String>();

porque sólo puedes usar Object como parámetro, no String, y por eso utilizamos los wildcards y extends, como muestro arriba.
Con las interfaces, sería lo mismo, solo que en lugar de extends, usaríamos implements.

A continuación te dejo un ejemplo de un código, donde creo una clase que va a ser un subtipo de ArrayList. Fíjate que por eso no necesitamos que mi clase implemente la interfaz List, porque eso ya lo hace ArrayList.
Además, vamos a hacer que implemente una interfaz que yo he creado, de esa manera podrás ver cómo se hace.

import java.util.*;
public  class TestGen{

    public static void main(String args[]){
   MyGen<String> miLista = new MyGen<String>();
miLista.add(0,"A");
miLista.add(1,"B");
miLista.add(2,"C");
for (String item: miLista){
System.out.println(item);
}
ArrayList<String> miOtraLista = new MyGen<String>();
miOtraLista.addAll(miLista);
for (String item: miOtraLista){
System.out.println(item);
}
if (miOtraLista.containsAll(miLista)){
System.out.println("Tienen los mismos elementos");
}
String miEjemplo = miLista.ejemplo().toString();
System.out.println(miEjemplo);
}

}
//La clase MyGen extiende ArrayList (que a su vez implementa List) y además implementa la interfaz //Ejemplo
class MyGen<String>  extends ArrayList<String> implements Ejemplo<Double>{
//aquí sustituímos T por Double, pero podríamos poner cualquier otro tipo
public MyGen(){
super();
}
//nuestra versión del método ejemplo
@Override
public Double ejemplo(){
return 15.25;
}
}
//creamos la interfaz usando T, de forma que cuando la implementemos podamos usar el tipo que //queramos.
 interface Ejemplo<T>{
T ejemplo();
}

De esta manera, la clase MyGen puede utilizar los métodos de ArrayList, que a su vez tiene los de las interfaces que implementa, y además va a poder usar el método de la interfaz Ejemplo.
Espero que ésto te ayude a aclarar los conceptos.

¡Un saludo!

sábado, 24 de noviembre de 2012

Animación de Imágenes en Java

votar

En esta entrada del blog, voy a enseñarte cómo hacer imágenes con animación en Java. Para ello, lo primero que vamos a necesitar son...lo has adivinado: imágenes. Pueden ser fotos, o pueden ser dibujos sencillos, pero eso sí, tienen que mostrar movimientos consecutivos; por ejemplo, un pájaro subiendo y bajando las alas, o una cara abriendo y cerrando los ojos.

Si sabes utilizar Photoshop, Gimp o cualquier otro programa por el estilo, enhorabuena, dale rienda suelta a tu imaginación para crear las imágenes que desees (y ya puestos, compártelas con nosotros).

Si por el contrario, eres como yo, que a lo más que llegas es a dibujar un círculo con Paint, pues nada, le pones dos circulitos más pequeños dentro para simular ojos abiertos y dos líneas para simular ojos cerrados y ya tienes una carita que parpadea. Ahora guardas los dos dibujos como .png y listo.

También puedes añadirle una imagen de fondo que ocupe toda la pantalla: una foto en formato .jpg, otro dibujo o un fondo liso, lo que quieras.

Lo que vamos a hacer con estas imágenes es, primero, crear un ArrayList para contenerlas, y después, asignarles a cada una una duración determinada en milisegundos. Por último, vamos a mostrarlas en pantalla en un bucle para conseguir ese efecto de animación.

Para ello vamos a crear una clase, Animación (qué sorpresa de nombre, ¿eh?) que contendrá los métodos necesarios. Despues haremos otra para probarla, y originales como somos la llamaremos AnimaciónTest. PruebaDeAnimación tampoco está mal, pero es que es más largo. Dentro de esta clase AnimaciónTest vamos a utilizar un objeto de la clase PantallaCompleta, que creamos en una entrada anterior y que utilizamos, nunca lo adivinarías, para visualizar el resultado en una pantalla completa . Aquí puedes ver el código de esa clase.

Para que luego no te líes con el código, te voy a explicar el significado de algunas variables que verás en él:
  • frames, que en inglés viene siendo fotogramas, es el nombre del ArrayList
  • actualFrame es el número de índice de un elemento de frames
  • tiempoAnimación es el tiempo que dura toda la animación
  • tiempoTotal es la suma de los milisegundos de duración que hemos asignado a cada imagen
Clase Animación.java

import java.awt.Image;
import java.util.ArrayList;

public class Animación{
   private ArrayList frames;
   private int actualFrame;
   private long tiempoAnimación;
   private long tiempoTotal;
   
   public Animación(){
      frames = new ArrayList();
   tiempoTotal = 0;
   start();
 }
 
 public synchronized void addFrame(Image image, long duración){
    tiempoTotal += duración;
    frames.add(new AnimFrame(image, tiempoTotal));
 }
 
 public synchronized void start(){
    tiempoAnimación = 0;
    actualFrame = 0;
 }
 
 public synchronized void update(long tiempoTranscurrido){
    if(frames.size()>1){
     tiempoAnimación += tiempoTranscurrido;
     if(tiempoAnimación >= tiempoTotal){
     tiempoAnimación = tiempoAnimación % tiempoTotal;
     actualFrame = 0;
  }
  while(tiempoAnimación > getFrame(actualFrame).endTime){
     actualFrame++;
  }
    }
 }
 
 public synchronized Image getImage(){
    if (frames.size() ==0){
       return null;
  }else{
     return getFrame(actualFrame).image;
  }
 }
 
 private AnimFrame getFrame(int i){
    return (AnimFrame)frames.get(i);
 }
 
 private class AnimFrame{
    Image image;
    long endTime;
    public AnimFrame(Image image, long endTime){
       this.image = image;
    this.endTime = endTime;
  }
 }
}
Clase AnimaciónTest.java

import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class AnimaciónTest {

    public static void main(String args[]) {

        DisplayMode displayMode= new DisplayMode(1024, 768, 32,
                DisplayMode.REFRESH_RATE_UNKNOWN);
        AnimaciónTest test = new AnimaciónTest();
        test.run(displayMode);
    }

    private static final long DEMO_TIME = 10000;

    private PantallaCompleta pc;
    private Image bgImage;
    private Animación animación;

    public void loadImages() {
        // cargamos las imágenes
        bgImage = loadImage("images/background.jpg");
        Image cara1 = loadImage("images/cara1.png");
        Image cara2 = loadImage("images/cara2.png");
        Image cara3 = loadImage("images/cara3.png");

        // creamos la animación
        animación = new Animación();
        animación.addFrame(cara1, 250);
        animación.addFrame(cara2, 150);
        animación.addFrame(cara1, 150);
        animación.addFrame(cara2, 150);
        animación.addFrame(cara3, 200);
        animación.addFrame(cara2, 150);
    }


    private Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    public void run(DisplayMode displayMode) {
        pc = new PantallaCompleta();
        try {
            pc.setFullScreen(displayMode, new JFrame());
            loadImages();
            animationLoop();
        }
        finally {
             pc.restoreScreen();
        }
    }


    public void animationLoop() {
        long tiempoInicio = System.currentTimeMillis();
        long tiempoActual = tiempoInicio;
        while (tiempoActual - tiempoInicio < DEMO_TIME) {
            long tiempoTranscurrido =
                System.currentTimeMillis() - tiempoActual;
            tiempoActual += tiempoTranscurrido;

            // actualizamos la animación
            animación.update(tiempoTranscurrido);

            // se dibuja en pantalla
            Graphics g =
                pc.getFullScreenWindow().getGraphics();
            draw(g);
            g.dispose();

            // una pequeña pausa
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) { }
        }

    }


    public void draw(Graphics g) {
        // se dibuja el fondo
        g.drawImage(bgImage, 0, 0, null);

        // se dibuja la imagen y la centramos más o menos
        g.drawImage(animación.getImage(), 300, 200, null);
    }

}
Yo he utilizado una imagen de fondo y tres dibujos de una cara, pero tú puedes usar el número de imágenes que quieras. Fíjate que cada imagen se puede añadir más de una vez, en distinto orden y con distinto tiempo asignado, para crear distintas animaciones. Tú puedes jugar con ello hasta encontrar la que te gusta.

En otro momento te enseñaré a añadirle otros efectos y a utilizar la clase BufferStrategy para  evitar que la imagen parpadee.

¡Si tienes alguna duda, ya sabes dónde estoy!

sábado, 17 de noviembre de 2012

Thread Pools en Java

votar

En otras entradas he explicado que son los hilos o threads, cómo crearlos y cómo funcionan. En esta entrada voy a tratar el tema de las thread pools, literalmente “piscinas de hilos”. ¿Por qué se llaman así? Bueno...ni idea. Pero ya que le han dado ese nombre, voy a utilizarlo en mi metáfora para explicarte en que consisten.

Imagínate una thread pool como un grupo de nadadores vagueando al borde de una piscina. De vez en cuando llega un entrenador con la orden de que uno de los nadadores tiene que recorrer la piscina de arriba abajo. A veces todos los nadadores reciben esas órdenes, un entrenador por cada nadador. En ocasiones hay más entrenadores que nadadores, y los últimos en llegar tienen que esperar a que vayan regresando los nadadores para ir dándoles de nuevo la orden de recorrer la piscina. Y otras veces sólo algunos nadadores están nadando mientras los otros están jugando a las cartas, esperando a que llegue algún entrenador.

Ahora sustituye “entrenador” por “tarea” y “nadador” por “hilo” y ya tienes una thread pool.

“Vale”, te preguntarás, “¿y por qué hacer una thread pool? ¿No es mejor ir creando los hilos según los necesitemos?”. Pues eso va a depender del programa en cuestión. Ten en cuenta que cada hilo que creas ocupa lugar en la memoria. Si tienes un programa que sólo va a tener tres o cuatro tareas distintas, pues bueno, creas tres o cuatro hilos. Pero, ¿y si es un programa con cientos o miles de tareas?. Cientos de hilos ocuparán bastante memoria. Y lo que es peor: tú tendrás que escribir el código para crear todos esos hilos.Créeme, es buena práctica separar en ese caso las tareas de los hilos en tu código. Y tú trabajarás menos.

Además, está la cuestión de la eficiencia. Ya sé lo que estás pensando: “¿No es más eficiente tener un hilo para cada tarea, en lugar de tener tareas esperando que haya un hilo libre?” Volvamos a nuestro ejemplo de la piscina.

Supón que en la piscina hay cien nadadores y que todos reciben la orden de nadar a la vez. ¿Has tratado alguna vez de nadar en una piscina abarrotada? ¿Crees que alguno de los nadadores conseguiría un récord de velocidad? Pues tu programa lo mismo. El sistema en el que se ejecute tendrá que repartir sus recursos entre todos los hilos a la vez.

Hagamos ahora que los nadadores sean sólo diez, con las mismas cien tareas. Es cierto que hay algunas tareas que tendrán que esperar a que los nadadores vayan regresando para realizarse, pero como los nadadores pueden ir y volver mucho más rápido, las primeras tareas serán “visto y no visto” y el tiempo de espera de las últimas será similar al producido si todas tuviesen un hilo propio. Y eso si todas las órdenes se reciben a la vez, porque si los entrenadores van llegando de forma separada, el tiempo de espera de los últimos no será mayor que el de los primeros.

Bueno, ya sabemos lo que es una thread pool, ¿pero cómo se utiliza?. Ah, aquí entran en juego los “Ejecutores”. No, no es una película de Schawzenegger. Los ejecutores en java son simplemente los que se encargan de manejar la ejecución de las tareas.

Java tiene una clase, Executors, con métodos estáticos para la creación de thread pools, y tres interfaces:
  • Executor, que simplemente ejecuta las tareas
  • ExecutorService, subinterfaz de la anterior que añade nuevos métodos para controlar las tareas.
  • ScheduledExecutorService, subinterfaz de ExecutorService que soporta la ejecución de tareas programadas.
Todas ellas forman parte del paquete java.util.concurrent.

A continuación, te voy a mostrar un ejemplo sencillísimo de thread pool. Vamos a crear dos clases, una que implementa la interfaz Runnable, porque los ejecutores trabajan con objetos "runnable" o "callable", y otra que contiene el método principal y donde crearemos una thread pool de 10 hilos.

Tarea.java

public class Tarea implements Runnable{
   private int sleepTime;
   private String name;
   public Tarea(String name){
    this.name = name;//le asignamos un nombre a cada tarea.
    sleepTime = 1000;
 }
   
   public void run(){
 try{
    System.out.printf("El hilo de la tarea "+this.name+" va a dormir durante %d milisegundos.\n",sleepTime);
    Thread.sleep(sleepTime);//hacemos que cada hilo duerma durante 1 segundo
 }catch(InterruptedException exception){
    exception.printStackTrace();
 }
 System.out.println("Este hilo ya ha dormido bastante");
 }
}


EjemploThreadPool.java

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EjemploThreadPool{
   public static void main (String args[]){
    System.out.println("Comienza la ejecución");
    ExecutorService ex = Executors.newFixedThreadPool(10);
 Tarea t;
 for(int i = 0;i<200; i++){
    t = new Tarea(""+i);
 
    ex.execute(t);
 }
    ex.shutdown();
    
   }
}


¿Qué es lo que hace este programa? No gran cosa. Se limita a escribir en la consola que el hilo que está ejecutando cada tarea, a las que hemos asignado un número, va a dormir durante 1 segundo. Pero, ¿y lo que has aprendido?

Fíjate que al método execute es al que hay que darle un objeto runnable, y que el método shutdown se asegura de cerrar al finalizar las tareas. Ambos métodos pertenecen a la interfaz ExecutorService. Para crear  la trhead pool hemos empleado el método newFixedThreadPool(), al que se le da un número específico de hilos. Pero existen más, como el newCachedThreadPool(), ambos de la clase Executors.

Para ampliar conocimientos, te recomiendo el tutorial sobre concurrencia de Oracle, y el libro "Java Threads", de S. Oaks y H. Wong, publicado por O´Reilly.

Y como siempre, si tienes alguna duda o comentario, ya sabes donde estamos.

sábado, 10 de noviembre de 2012

Cómo crear una pantalla completa para juegos en Java

votar
Supongamos que has estado aprendiendo Java durante algún tiempo; ya sabes todo lo que hay que saber sobre clases, objetos, métodos, polimorfismo, threads...Pero ahora quieres hacer algo realmente espectacular, como un juego (no para presumir, por supuesto, sólo para ampliar conocimientos). Y no un juego cualquiera, no una pequeña applet ni un juego embebido en una frame, sino un juego a pantalla completa, sin menús, ni barras horizontales o laterales que distraigan al jugador. Pero, ¿por dónde empezar?

Bueno, evidentemente, excede los límites de cualquier entrada de un blog el explicarte cómo hacer un juego en Java, pero lo que sí puedo hacer es mostrarte cómo crear una pantalla a tamaño completo para él. Antes de nada, vamos a aclarar algunos conceptos que necesitas saber (si ya los sabes, pues te vas directamente al código y listo).

Al diseñar un juego, hay que tener en cuenta que se tiene que poder ver en distintos ordenadores, de los que no vas a saber la resolución de los monitores ni la capacidad de la tarjeta de vídeo.¿Qué es la resolución? La pantalla de un monitor está dividida en pequeños puntitos llamados píxeles. El número de píxeles horizontales y verticales es la resolución de esa pantalla. Por ejemplo, un monitor con 800 píxeles horizontales y 600 píxeles verticales tiene una resolución de 800x600. Ten en cuenta que los píxeles se empiezan a contar desde 0, es decir 0 es el primer píxel y 799 el último.

Si tú pones una resolución en la pantalla de tu juego, pero el monitor tiene otra, la imagen no será tan nítida. Lo ideal es que el jugador pueda elegir la resolución según su monitor. Las resoluciones más utilizadas hoy en día son 800x600, 1024x768 y 1280x1024. En el código que verás después, hemos puesto una resolución fija, para no hacerlo muy extenso, pero tú puedes poner la de tu propio monitor si no es la misma.

Otro detalle en el que hay que fijarse, es en el Display Mode, o capacidad de bits de un monitor, que determina el número de colores que ese monitor puede mostrar. En nuestro ejemplo vamos a dejarlo en 32, que son 2 elevado a 32  colores (¿tienes una calculadora científica a mano? ¿no? bueno, son muchos colores) Si tu monitor es un poco más antiguo o prefieres aumentar la rapidez, puedes usar 16 bit, que son (esta me la sé) 65.536 colores.

Por último tenemos el Refresh Rate, que viene siendo la rapidez con la que la pantalla se está "repintando" una y otra vez. Lo que a ti te parece una imagen fija, en realidad no lo es, porque la luz de los píxeles se desvanece y el monitor está continuamente "encendiéndolos" de nuevo.

Ahora que ya hemos aclarado estos aspectos, para que sepas lo que son cuando los veas en el código, te voy a explicar lo que vamos a hacer. Vamos a crear una ventana, como las típicas de Windows, pero sin ninguna decoración, es decir, sin menús ni barras, ni botoncito de ningún tipo. En un juego normal, por supuesto, tienes la opción de salir cuando quieras, pero aquí, para favorecer la claridad del código y de lo que quiero enseñarte, vamos a dejar que esa pantalla se mantenga unos segundos y luego se desvanezca, que no es cuestión de que te quedes para siempre con ella. Si deseas que tenerla durante más o menos segundos, sólo tienes que cambiar el valor que yo le he puesto.

Hay algunos sistemas que no dejan que cambies el display que tienen por defecto, por lo que tendremos que  asegurarnos primero de que ése no es el caso. 

Así que vamos a hacer dos clases, la primera será la que tenga el constructor de la pantalla, y la segunda será la que tenga el método main, donde probaremos a ver si funciona. Con ellas crearemos una pantalla completa con un color de fondo naranja y una frase en negro, que se mantendrá durante 10 segundos.

PantallaCompleta.java

import java.awt.*;
import javax.swing.JFrame;

public class PantallaCompleta{
   private GraphicsDevice gd;//tarjeta gráfica
   public PantallaCompleta(){
      //hay que usar los medios del propio sistema
      GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
   gd = env.getDefaultScreenDevice();
 }
 
 public void setFullScreen(DisplayMode dm, JFrame ventana){
    ventana.setUndecorated(true);
    ventana.setResizable(false);
    gd.setFullScreenWindow(ventana);
    
    //comprobamos que el sistema soporta el cambio
    if(dm != null && gd.isDisplayChangeSupported()){
       try{
      gd.setDisplayMode(dm);
    }catch(IllegalArgumentException e){}
  }
 }
 
 public Window getFullScreenWindow(){
    return gd.getFullScreenWindow();
 }
 
 //restauramos los valores previos
 public void restoreScreen(){
    Window w = gd.getFullScreenWindow();
    if(w != null){
       w.dispose();
  }
  gd.setFullScreenWindow(null);
 }
}

TestPantallaCompleta.java

import java.awt.*;
import javax.swing.JFrame;

public class TestPantallaCompleta extends JFrame{
   public static void main(String[]args){
      //Pon aquí los valores que prefieras
      DisplayMode dm = new DisplayMode(1024,768,32,DisplayMode.REFRESH_RATE_UNKNOWN);
   TestPantallaCompleta test = new TestPantallaCompleta();
   test.run(dm);
 }
 
 public void run (DisplayMode dm){
    setBackground(Color.ORANGE);
    setForeground(Color.BLACK);
    setFont(new Font("Dialog", Font.PLAIN,48));
    PantallaCompleta pc = new PantallaCompleta();
    try{
       pc.setFullScreen(dm, this);
    try{
      //ponemos el tiempo que queremos que dure
      Thread.sleep(10000);
    }catch(InterruptedException ex){}
  }finally{//pase lo que pase, se restaura a sus antiguos valores
     pc.restoreScreen();
  }
 }
 
 public void paint(Graphics g){
    if (g instanceof Graphics2D){
       Graphics2D g2 = (Graphics2D) g;
    //difuminamos los bordes del texto
    g2.setRenderingHint(
       RenderingHints.KEY_TEXT_ANTIALIASING,
       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    }     
           //el texto va a aparecer 300px a la derecha y 400px hacia abajo
    g.drawString("Aquí va a ir mi juego",300,400);
 }
}

Eso de "antialiasing" que ves al final del código, no es que no dejes a los jugadores tener un alias, sino que sirve para que el texto aparezca como escalonado, como escrito en cuadraditos.

A continuación te dejo una captura de pantalla para que veas cómo queda con los valores que yo le he asignado.



Si quieres más información sobre cómo crear juegos en Java, te recomiendo el libro Developing Games in Java, de David Brackeen, Bret Barker y Lawrence Vanhelsuwe, editorial New Riders Publishing. El código que te muestro aquí es una versión del que aparece en este libro. No sé si hay traducción en castellano, pero como creo que es muy interesante es seguro que en otras entradas os muestre otros ejemplos sacados de él.

Además he encontrado unos tutoriales en vídeo en Youtube, que su autor no lo dice, pero yo juraría que están basados en este libro. Están en inglés, pero aunque no lo entiendas, puede que el código que muestra te sea útil. Podéis encontralos en esta página.

domingo, 4 de noviembre de 2012

Drag and Drop: cómo funciona

votar
¡Después de unos cuantos meses de ausencia, por fin he vuelto! No, no me he ido de vacaciones (que más quisiera), sino que el imponderable deber de tener que trabajar para vivir se interpuso entre este blog y yo. ¡Y mira que os pongo anuncios para que clickéis y me libréis de ese problema! Pero, nada, no os dáis por enterados. ;-)

Hablando ahora ya en serio, esta entrada esta dedicada al Drag and Drop. Para los que no sepáis lo que es, os diré que no se trata del nombre de un grupo pop, sino de ese efecto de arrastrar elementos de un lugar a otro, como cuando arrastramos ficheros de una carpeta al escritorio del ordenador.

En principio, todos los componentes de Swing soportan el Drag and Drop, pero algunos tienen instalada la forma de ejecutarlo por defecto y otros no. Para los primeros, basta con invocar el método setDragEnabled(true) y listo. Para los segundos, hay que escribir el código necesario para adaptarlo a tus necesidades.

Los componentes que tienen un mecanismo de arrastre por defecto son:
  • JColorChooser
  • JEditorPane
  • JFileChooser
  • JFormattedTextField
  • JList
  • JTable
  • JTextArea
  • JTextPane
  • JTree
En cuanto al mecanismo para "dejar caer" los datos que queremos transferir, en estos componentes no hay que hacer nada más:
  • JEditorPane
  • JFormattedTextField
  • JPasswordField
  • JTextArea
  • JTextField
  • JTextPane
  • JColorChooser
mietras que estos otros necesitan un poco más de código para funcionar:
  • JList
  • JTable
  • JTree
Para los componentes que no tienen un método de trasnferencia de datos por defecto, deberemos usar la clase TransferHandler.

A continuación voy a poneros un par de ejemplos, simplificados hasta el extremo, para que veáis el funcionamiento del Drag and Drop en Java. En el primero crearemos dos selectores de color y arrastraremos el color seleccionado en el primero hasta el segundo. Fijaos que no podemos arrastrar desde los cuadraditos pequeños, sino desde la vista previa. Este primer ejemplo es una adaptación del que podéis encontrar en la siguiente página.

import java.awt.BorderLayout;
import javax.swing.JColorChooser;
import javax.swing.JFrame;

public class DobleColor {
  public static void main(String args[]) {
    JFrame frame = new JFrame("Doble Selector de Color ");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JColorChooser izquierda = new JColorChooser();
    izquierda.setDragEnabled(true);
    frame.add(izquierda, BorderLayout.WEST);
    JColorChooser derecha = new JColorChooser();
    derecha.setDragEnabled(true);
    frame.add(derecha, BorderLayout.EAST);

    frame.pack();
    frame.setVisible(true);
  }
Podéis ver aquí cómo quedaría, tras haber arrastrado el color seleccionado desde el primero hasta el segundo.



En el siguiente ejemplo, vamos a arrastrar un texto desde una etiqueta o JLabel a un campo de texto o JTextField. Para ello utilizaremos los método setTransferHandler y getTransferHandler, además de crear un evento al presionar con el ratón, para que funcione el gesto de arrastrar.


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.JFrame;

public class SimpleDnDs extends JFrame{
    //Vamos a crear componentes
    private JTextField textField;
    private JLabel label;
    private JPanel panel;
 
 public static void main (String args[]){
     SimpleDnDs dnd = new SimpleDnDs();
 }
     
    public SimpleDnDs(){ //constructor de SimpleDnDs
      JFrame frame = new JFrame("Drag and Drop");
      panel = new JPanel();   
      textField = new JTextField(40);
      label = new JLabel("Este es el texto a arrastrar");
   
   //especificamos qué tipo de dato vamos a transferir
      label.setTransferHandler(new TransferHandler("text"));
      MouseListener ml = new MouseAdapter(){
     //creamos el método para transferir
     //datos al presionar con el ratón
        public void mousePressed(MouseEvent e){
        JComponent jc = (JComponent)e.getSource();
        TransferHandler th = jc.getTransferHandler();
        th.exportAsDrag(jc, e, TransferHandler.COPY);
        }
      };
      label.addMouseListener(ml);
   panel.add(label);
   panel.add(textField);
   frame.add(panel); //añadimos el panel al marco
      frame.setSize(600,600); //le ponemos las medidas que queramos
      frame.setLayout(new FlowLayout()); 
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
      frame.setLocationRelativeTo(null);
      frame.setVisible(true); 
   }
}
Una vez ejecutado el programa, podréis arrastrar el texto desde la etiqueta al campo de texto. Es por supuesto muy sencillito, sólo para que veáis cómo funciona, y podáis crear vuestros propios programas, jugando con distintos componentes y tipos de datos a transferir.

Para ello, recomiendo a los que sepáis inglés que echéis un ojo al tutorial de Oracle sobre Drag and Drop. Y como siempre, si tenéis dudas o comentarios, ya sabéis cómo. Además, si queréis saber cuándo publico las siguientes entradas sin tener que estar pendientes del blog, os recomiendo que os suscribáis.


sábado, 16 de junio de 2012

JDBC: Java y las bases de datos

votar
JDBC  es el nombre de una librería Java que posee un conjunto de interfaces y clases utilizadas para escribir programas Java que pueden acceder y manipular bases de datos relacionales. Al utilizarla, las aplicaciones escritas en Java pueden ejecutar sentencias SQL, presentar los datos en interfaces amigables para los usuarios y cambiar datos en la base de datos.

JDBC puede ser utilizada tanto con bases de datos MySQL, como bases de datos Oracle o con Access de Microsoft. Pero para poder utilizar esta librería y acceder a las bases de datos en los programas hay que instalar los drivers correspondientes. Puesto que un driver JDBC es la interfaz que facilita las comuncaciones entre JDBC y la base de datos, es un driver específico de la base de datos. Así pues, hace falta un MySQL JDBC Driver para acceder a una base de datos MySQL, un Oracle JDBC Driver para acceder a una base de datos Oracle, pero para acceder a una base de datos Access, el JDK incluido trae un puente o Bridge JDBC-ODBC. ODBC es una tecnología desarrollada por Microsoft para el acceso a las bases de datos en la plataforma Windows. A su vez, un driver ODBC viene preinstalado en Windows, así que el puente JDBC-ODBC permite el acceso a cualquier base de datos ODBC de un programa Java.

Los drivers de MySQL y Oracle son:
   MySQL: com.mysql.jdbc.Driver
   Oracle: oracle.jdbc.driver.OracleDriver
que se pueden descargar de las compañías correspondientes. En concreto, el driver de MySQLestá en el archivo jar mysqljdbc.jar. Una vez descargado el driver, hay que añadirlo al classpath, ya sabéis: Equipo>>Propiedades>>Opciones avanzadas>>Variables del entorno...si lo habéis hecho al instalar Java, ya os lo sabéis de memoria.

Y ahora, ¿cómo se usa?. Bueno vamos a ver un ejemplo muy sencillito para que os hagáis una idea. Lo voy a hacer con MySQL, que es la base de datos que uso yo, pero también vale para cualquiera de las otras. Suponed que tenéis una base de datos de un colegio, y en ella una tabla con los datos personales de los estudiantes. Como este blog no es sobre SQL, vamos a hacerlo sin muchas explicaciones: 

CREATE DATABASE colegio;
USE colegio;
CREATE TABLE alumnos(
     nombre varchar(15),
     primer_apellido varchar(15),
     segundo_apellido varchar(15),
     nacimiento date,
     teléfono char(10),
     dirección varchar(20),
     );

Y a continuación la pobláis con los datos de alumnos imaginarios. Ahora vamos a crear un programa que se conecte a la base de datos y de como salida solo los nombres y los apellidos de los alumnos.



import java.sql.*; //no olvidéis esto
public class EjemploJdbc{
   public static void main(String[] args)throws SQLException, ClassNotFoundException{ //podemos usar try-catch, pero así el ejemplo es más simple
      Class.forName("com.mysql.jdbc.Driver"); //se carga el driver
      System.out.println("Driver cargado"); 

      //Ahora vamos a establecer una conexión
      Connection conexión = DriverManager.getConnection
       ("jdbc:mysql://localhost/colegio","usuario","contraseña");
     //Hemos puesto la base de datos con la que queremos conectar y el nombre de usuario y contraseña que usamos con ella
      System.out.println("Base de datos conectada");

      Statement st = conexión.createStatement();
      ResultSet resultado = st.executeQuery
         ("select nombre,primer_apellido,segundo_apellido from alumnos");

      //iteramos
      while(resultado.next()){
         System.out.println(resultado.getString(1)+ "\t"+ resultado.getString(2) + "\t" + resultado.getString(3));
      }
      conexión.close(); //cerramos la conexión
   }
}


La salida serán los nombres de los alumnos que hayáis introducido en la tabla. Ya véis que es muy sencillo. Se pueden usar cualquier sentencia SQL dentro del método executeQuery(String sql). El resultado será adjudicado a ResultSet. 


Por supuesto JDBC es mucho más amplio, y en otra ocasión veremos alguna de sus interfaces y sus clases. Por ahora, espero que esto os haya servido para poder conectar vuestros programas Java a vuestras bases de datos.

sábado, 9 de junio de 2012

Formateo de textos en Java

votar
Para formatear textos, Java utiliza los métodos format() y printf(), que en realidad tienen las mismas funcionalidades. Por ejemplo, podemos hacer
   
   System.out.printf(%s,%s, "Super","ejemplo");

o

   String str = String.format(%s,%s,"Super","Ejemplo");

A los que vengáis de C o algún otro lenguaje esto os resultará familiar, ya que es similar a los métodos printf y sprintf. Eso sí, tened en cuenta que Java arroja una excepción si encuentra un String mal formateado.

A los que veis esto por primera vez y os suena a chino, os lo voy a explicar con más detalle.
Supongamos que tenéis un texto:

   String s = "El número de visitantes el "+nombredia+" fue de "+numerovisitantes;

Formateando, lo dejaríamos así:

   String s = String.format("El número de visitantes el %s fue de %d", nombredia, numerovisitantes);

¿Pero que son %s y %d?
Bueno, eso son carecteres de conversión y los veremos en un momento, ahora sólo os digo que s significa string y d un número entero.

La clase java.util.Formater es la que proporciona la base para la representación de texto formateado a través de sus métodos

   format(String format, Object...args);
   format(Locale l, String format, Object...args);

El texto resultante puede ser un String, un StringBuilder, un archivo o cualquier OutputStream.
Las dos versiones del método format() también son utilizadas por las clases java.io.PrintStream y java.io.PrintWriter, que además tienen dos versiones similares del método printf().
La clase String tiene un método format(), pero es estático.

La sintaxis es la siguiente: el primer parámetro del método es el texto y los especificadores de formato, seguido de un array de argumentos.
Los especificadores se escriben en el siguiente orden (entre corchetes los que son opcionales):

   %[argument_index] [flags] [width] [precision] conversion

donde % marca el inicio de un especificador.
[argument_index] indica cuál de los argumentos se va a utilizar. Se hace con un número seguido de $. Por ejemplo:

   String str = "Año, mes, día: %3$s-%2$-%1$s";
   System.out.printf(str, 5, "Febrero", 2012);

producirá la salida

   Año, mes, día: 2012-Febrero-5

Los flags o banderas son opcionales, e indican (los coloco entre comillas aquí, pero no las llevarían en el código):

   "-":  justifica el argumento a la izquierda.
   "#":  incluye un radical para enteros y una coma para decimales.
   "+":  incluye el signo(+ o -).
   "  ":  espacio en los valores positivos.
   "0":  se llenan los espacios con ceros en los valores positivos.
   ",":  usa un separador de grupo específico según la zona geográfica.
   "(":  encierra los números negativos en paréntesis.

Width indica el número mínimo de caracteres que tendrá la salida. 
Precision indica el número máximo de dígitos después de la como en un decimal.

El especificador conversion es obligatorio e indica de qué tipo es el argumento. Los más usados son

   b: boolean.
   c: carácter Unicode.
   d: número entero.
   f: número decimal
   s: String.
   t: fecha y hora.

Veamos ahora un par de ejemplos para que lo acabéis de entender.

 int in = 1234;

 String s = String.format("El número es: %08d", in);

 System.out.println(s);


La salida será: 


   00001234


Vemos que se han añadido ceros a la izquierda, porque le hemos dado una anchura de ocho dígitos y queremos que lo que falta lo ocupen ceros.


public class EjempoFormat{
   public static void main(String[] args){
      String nombre = args[0];
      String apellido = args[1];
      String s2 = String.format("Datos:\n%1$-10s\n%2$-10s\n",nombre,apellido);
      System.out.println(s2);
   }
}

En este caso la salida serán los argumentos que introduzcamos nosotros alineados a la izquierda. Por ejemplo:


Datos:
Alfonso
López


Se pueden hacer por supuesto cosas mucho más complicadas, pero espero que esto al menos os sirva para empezar. :-)

viernes, 1 de junio de 2012

Java Regex

votar
   Vamos a tratar en esta entrada las expresiones regulares en Java. Será sólo una aproximación, porque esto de las expresiones regulares, regex para abreviar, es un lenguaje en sí mismo y da para varios libros. Las expresiones regulares no se utilizan sólo en Java, si no también en otros lenguajes de programación, como Pearl o Groovy, y por supuesto las puedes usar en Word o Writer. Sin embargo, en cada lenguaje existen ligeras diferencias a la hora de usarlas. Como si no fuera ya bastante complicado el tema. Lo que sí tienen en común es que se usan para encontrar y manipular texto.

Una expresión regular sustituye desde un simple carácter a oraciones enteras que deseas encontrar en un texto. Evidentemente, si quieres encontrar más de una palabra, o incluso una palabra larga, no es muy práctico tener que escribirlas literalmente.

Java tiene dos clases, dentro del paquete java.util.regex, especializadas en este tipo de manipulación de texto: Matcher y Pattern. Pattern compila una expresión regular para crear un patrón. Matcher utiliza dicho patrón para hallar coincidencias en el texto.
Por ejemplo:

String texto = “Este es el texto en el que vamos a buscar un patrón”;
String patrónDeTexto = “.*el.*”; //.* significa cero o más caracteres antes y después de la palabra.
Pattern patrón = Pattern.compile(patrónDeTexto); //creamos una instancia de Pattern
Matcher match = patrón.matcher(texto); //creamos una instancia de Matcher
boolean b = match.matches();
System.out.println(“Coincide: “+ b);

Si la expresión regular sólo la vamos a usar una vez, podemos utilizar el método matches() con Pattern sin necesidad de una instancia de Matcher, simplificando lo anterior así:

String texto = “Este es el texo en el que vamos a buscar un patrón”;
String patrónDeTexto = “.*el.*”;
boolean b = Pattern.matches(patrónDeTexto, texto);
System.out.println(“Coincide: “+b);

lo que nos daría la misma salida que en el caso anterior:
Coincide: true


Supongamos ahora que queremos saber exáctamente cuántas coincidencias hay en el texto de la palabra “el” y en qué lugar se encuentran. Para ello utilizaremos los métodos de Matcher find(), start() y end(), teniendo en cuenta que el índice comienza en cero y que los espacios en blanco también cuentan como caracteres.

String texto = “Este es el texto en el que vamos a buscar un patrón”;
String patrónDeTexto = “el”; //queremos saber cuántas veces aparece esta palabra.
Pattern patrón = Pattern.compile(patrónDeTexto);
Matcher match = patrón.matcher(texto);
int contador = 0;
while (match.find()){
contador++;
System.out.println(“Coincidencia: “ + contador + “ de ” + match.start() + “ a ” + matcher.end());
}

La salida en este caso será la siguiente:
Coincidencia: 1 de 8 a 10
Coincidencia: 2 de 20 a 22

Otro método interesante es split(), utilizado para separar el texto en distintas líneas, de esta manera:

String texto = “Este es el texto, en el que vamos a buscar, un patrón”);
String patrónDeTexto = “,”;
Pattern patrón = Pattern.compile(patrónDeTexto);
String[] split = patrón.split(texto);
for(String palabras : split){
System.out.println(palabras);
}

La salida en este caso es:
Este es el texto
en el que vamos a buscar
un patrón

Hasta ahora hemos visto unos cuantos métodos de las clases Pattern y Matcher, pero poco sobre las expresiones regulares en sí. Pero como ya hemos dicho, utilizar expresiones literales suele ser muy inconveniente. Veamos ahora algunas de las expresiones regulares más utilizadas en Java.

.    equivale a cualquier carácter
\d equivale a cualquier dígito
\D equivale a cualquier carácter no dígito
\s equivale a un espacio en blanco
\S equivale a cualquier carácter excepto un espacio en blanco
\w equivale a cualquier letra
\W equivale a cualquier carácter excepto letras
[abc] equivale a a, b o c (o cualquier otro carácter que se indique)
[^abc] equivale a cualquier carácter excepto a, b o c
[a-zA-Z] equivale a cualquier carácter de la a a la z o de la A a la Z, todos inclusive
[a-d[m-p]] equivale a cualquier carácter de la a la d o de la m a la p
[a-z&&[def]] equivale a d, e o f
[a-z&&[^bc]] equivale a cualquier carácter de la a a la z excepto b o c
[a-z&&[^m-p]] equivale a cualquier carácter de la a a la z excepto los que van de la m a la p


Además, en java regex se utilizan cuantificadores para indicar el número de veces que se repiten los caracteres buscados. Estos cuantificadores se denominan greedy, reluctant y possessive. Para indicar el cuantificador greedy, siendo X un carácter cualquiera, se escribe:
X* para buscar coincidencias de cero o más X
X? para buscar coincidencias de una o ninguna X
X+ para buscar coincidencias de una o más X

Para denotar el cuantificador reluctant, a los signos anteriores se añade ?, de esta manera:
X*?
X??
X+?
Y para el cuantificador possessive, el signo +:
X*+
X?+
X++

¿Cómo funcionan estos cuantificadores? Bueno, el cuantificador greedy “lee” todo el texto de una vez y luego, de derecha a izquierda, se va deshaciendo de caracteres y vuelve a leer lo que queda una y otra vez hasta encontrar una coincidencia. El cuantificador reluctant, en cambio, empieza leyendo el texto de iquierda a derecha hasta hallar la coincidencia buscada. El cuantificador possessive lee el texto entero como el greedy, pero solo una vez, y si no haya una coincidencia esa vez, no sigue buscando más.

Veamos esto con un ejemplo. Supongamos que tenemos el siguiente texto “ID usuario Nombre usuario” y buscamos cero o más coincidencias de la siguiente expresión regular .io(cualquier carácter seguido de io)
Greedy: .*io. Leería el texto entero, que no está seguido de io, e iría “soltando” caracteres hasta que encontrase ID usuario Nombre usuar, seguido de los caracteres io que habría soltado y encontraría una coincidencia:
ID usuario Nombre usuario.
Reluctant: .*?io. Iría leyendo el texto de izquierda a derecha y encontaría dos coincidencias:
ID usuario
Nombre usuario
Possessive: .*+io. Leería todo el texto, pero solo una vez, y claro, como el texto no está seguido de io, no encontaría ninguna coincidencia.

Como hemos dicho, esto solo es una breve aproximación a las expresiones regulares en Java, pero esperamos que os sirva para ir practicándolas, así como a entender cómo funcionan las clases Pattern y Matcher.