Herramientas de usuario

Herramientas del sitio


apuntes:ficheros

Acceso a ficheros

Tipos de ficheros

Desde el punto de vista de un programador solamente distinguiremos entre dos tipos de ficheros:

  • Ficheros de texto cuando el contenido del fichero contenga exclusivamente caracteres de texto (podemos leerlo con un simple editor de texto)
Extensión Tipo de fichero
.txt Fichero de texto plano
.xml Fichero XML
.json Fichero de intercambio de información
.props Fichero de propiedades
.conf Fichero de configuración
.sql Script SQL
.srt Fichero de subtítulo
  • Ficheros binarios cuando no estén compuestos exclusivamente de texto. Pueden contener imágenes, videos, ficheros, . . . aunque también podemos considerar un fichero binario a un fichero de Microsoft Word en el que sólo hayamos escrito algún texto puesto que, al almacenarse el fichero, el procesador de texto incluye alguna información binaria
Extensión Tipo de fichero
.pdf Fichero PDF
.jpg Fichero de imagen
.doc, .docx Fichero de Microsoft Word
.avi Fichero de video
.ppt, .pptx Fichero de PowerPoint

A veces, en ficheros binarios, podremos encontrarnos con las extensiones .bin o .dat para hacer referencia a ficheros que contienen información binaria en un formato que no está ampliamente difundido. Serán simplemente ficheros que una aplicación determinada es capaz de leer/escribir de una forma específica solo definida para dicha aplicación.

Propiedades del Sistema

En cualquier caso, para el acceso a ficheros independientemente del tipo de los mismos, conviene conocer el funcionamiento de las System Properties de las que dispone Java en su API. Éstas permiten acceder a propiedades de la configuración del sistema y, entre otras, podemos encontrarnos con algunas muy interesantes relacionadas con este tema:

  • “file.separator” Obtiene el caracter, según el S.Operativo, para la separación de las rutas (/ ó \). También se puede utilizar la constante File.separator
  • “user.home” Obtiene la ruta de la carpeta personal del usuario (que dependerá del S.Operativo en casa caso)
  • “user.dir” Obtiene la ruta en la que se encuentra actualmente el usuario
  • “line.separator” Obtiene el caracter que separa las líneas de un fichero de texto (difiere entre Windows/Linux)

En el caso de que se quiera acceder al valor de alguna de estas propiedades debe hacerse utilizando la llamada al método System.getProperty(String)

System.out.println("La carpeta de mi usuario es " + System.getProperty("user.home"));

Ficheros de texto

En esta parte vamos a trabajar con 3 tipos de ficheros de texto:

  • Ficheros de texto plano que contendrán texto libre y donde podremos escribir sin respetar ningún tipo de formato
  • Ficheros de configuración que contendrán información de configuración para una aplicación. Tienen un formato específico y Java además proporciona una API para trabajar más cómodamente con ellos
  • Ficheros XML que contienen información y acompañada de etiquetas que le dan significado. Tienen unas reglas y formato más o menos definido y Java proporciona una API para trabajar con ellos

Existen más tipos de ficheros de texto también muy extendidos como .json y .csv pero no serán estudiados en esta parte.

Ficheros de texto plano

Los ficheros de texto son aquellos que únicamente contienen texto, por lo que pueden ser editados directamente con cualquier editor de texto plano (Bloc de Notas, notepad++, . . .). Se podría decir que son aquellos ficheros que podrían ser leídos por cualquier persona. Son aquellos que normalmente se almacenan con la extensión .txt pero también podríamos incluir los scripts SQL (.sql), ficheros de código Java (.java), ficheros de configuración (.ini, .props, .conf, . . .), . . .

También se incluyen en la categoría de ficheros de texto los que además incluyen información adicional (siempre en forma de texto) que permiten interpretar los datos del fichero de una manera u otra, añadiendo más información al mismo. Estos formatos son HTML, XML, JSON, . . .

En cualquier caso, desde Java siempre se podrán leer/escribir de la misma manera, según veremos a continuación. También veremos como pueden leerse/escribirse utilizando librerías aquellos ficheros de texto plano que contienen formato, como hemos comentado anteriormente, haciendo de esa forma mucho más fácil el trabajo.

Escribir ficheros de texto plano

PrintWriter escritor = null;
 
 
escritor = new PrintWriter("archivo.txt") ;
escritor.println("Esto es una linea del fichero");
escritor.close();

Leer ficheros de texto plano

File fichero = new File("fichero.txt");
Scanner lector = null;
 
lector = new Scanner(fichero);
while(lector.hasNextLine()){
   System.out.println(lector.nextLine());
}
lector.close();

Otras formas de escribir/leer en ficheros de texto

  • Escribir Con FileWriter
FileWriter writer = new FileWriter("fichero.txt");
writer.write("Texto a fichero \n");
writer.close();
 
 
//Si quiero que no me sobrescriba el fichero destino
FileWriter writer = new FileWriter("fichero.txt", true);
  • Con BufferedWriter / Reader
BufferedWriter writer = new BufferedWriter("fichero.txt");
writer.write("texto a escribir\n");
writer.close();
 
 
BufferedReader reader = new BufferedReader("fichero.txt");
cadena = reader.readLine(); //Debo leer antes porque igual no hay lineas
 
//Mientras haya lineas para leer (no devuelva null)
while( cadena != null ){
   System.out.println(cadena);
   cadena = reader.readline();
}
reader.close();

Ficheros de configuración

En la API de Java se incluyen librerías para trabajar con los ficheros de configuración. Puesto que todos siguen un mismo patrón, es la librería la que se encarga de acceder al fichero a bajo nivel y el programador sólo tiene que indicar a que propiedad quiere acceder o que propiedad quiere modificar, sin tener que añadir nada de código para leer o escribir el fichero tal y como hemos visto en el punto anterior.

Un ejemplo de fichero de configuración de esta librería de java sería el fichero que sigue:

configuracion.conf
# Fichero de configuracion
# Thu Nov 14 10:49:39 CET 2013
 
user=usuario
password=micontrasena
server=localhost
port=3306

Escribir ficheros de configuración

Así, si queremos generar, desde Java, un fichero de configuración como el anterior:

. . .
Properties configuracion = new Properties();
configuracion.setProperty("user", miUsuario);
configuracion.setProperty("password", miContrasena);
configuracion.setProperty("server", elServidor);
configuracion.setProperty("port", elPuerto);
try {
  configuracion.store(new FileOutputStream("configuracion.conf"), 
                                           "Fichero de configuracion");
} catch (FileNotFoundException fnfe ) { 
  fnfe.printStackTrace(); 
} catch (IOException ioe) { 
  ioe.printStackTrace();
}
. . .

Leer ficheros de configuración

A la hora de leerlo, en vez de tener que recorrer todo el fichero como suele ocurrir con los ficheros de texto, simplemente tendremos que cargarlo e indicar de qué propiedad queremos obtener su valor (getProperty(String)).

. . .
Properties configuracion = new Properties();
try {
  configuracion.load(new FileInputStream("configuracion.conf"));
  usuario = configuracion.getProperty("user");
  password = configuracion.getProperty("password");
  servidor = configuracion.getProperty("server");
  puerto = Integer.valueOf(configuration.getProperty("port"));
} catch (FileNotFoundException fnfe ) { 
  fnfe.printStackTrace();
} catch (IOException ioe) { 
  ioe.printStackTrace();
}
. . .

Para ambos casos, escribir y leer este tipo de ficheros, hay que tener en cuenta que, al tratarse de ficheros de texto, toda la información se almacena como si de un String se tratara. Por tanto, todos aquellos tipos LocalDate, boolean o incluso cualquier tipo numérico serán almacenados en formato texto. Así, habrá que tener en cuenta las siguientes consideraciones:

  • Para el caso de las fechas, deberán ser convertidas a texto cando se quieran escribir y nuevamente reconvertidas a LocalDate cuando se lea el fichero y queramos trabajar con ellas
  • Para el caso de los tipos boolean, podemos usar el método String.valueOf(boolean) para pasarlos a String cuando queramos escribirlos. En caso de que queramos leer el fichero y pasar el valor a tipo boolean podremos usar el método Boolean.parseBoolean(String)
  • Para el caso de los tipos numéricos (integer, float, double) es muy sencillo ya que Java los convertirá a String cuando sea necesario al escribir el fichero. En el caso de que queramos leerlo y convertirlos a su tipo concreto, podremos usar los métodos Integer.parseInt(String), Float.parseFloat(String) y Double.parseDouble(), según proceda

Ficheros XML

Los ficheros XML permiten el intercambio de información entre aplicaciones utilizando para ello un fichero de texto plano al que se le pueden añadir etiquetas para darle significado a cada uno de los valores que se almacenan.

Por ejemplo, el siguiente fichero XML podría ser el resultado de volcar una Base de Datos sobre productos de una compañia, de forma que dicha información podría ahora leerse desde otra aplicación e incorporarla. Se puede ver como a parte de los datos de dichos productos (sus nombres, precios, . . .) aparece otra información en forma de etiquetas (entre los caracteres < y >) que permite dar significado a cada dato almacenado en el fichero. Así, podemos saber de qué estamos hablando y a qué corresponde cada valor.

Obviamente, tal y como ocurría con los ficheros de configuración, toda la información se tiene que pasar a texto para poder crear el fichero y reconvertida a su tipo original cuando se cargue de nuevo. Las mismas consideraciones que hemos tenido en cuenta antes nos servirán ahora (ver aqui)

productos.xml
<?xml version="1.0" encoding="UTF-8" standalone="no">
<xml>
<productos>
  <producto>
    <nombre>Cereales</nombre>
    <precio>3.45</precio>
  </producto>
  <producto>
    <nombre>Colacao</nombre>
    <precio>1.45</precio>
  </producto>
  <producto>
    <nombre>Agua mineral</nombre>
    <precio>1.00</precio>
  </producto>
</productos>
</xml>
Producto.java
public class Producto {
  private String nombre;
  private float precio;
 
  public Producto(String nombre, float precio) {
    this.nombre = nombre;
    this.precio = precio;
  }
 
  public String getNombre () { return nombre; }
  public void setNombre (String nombre) { this.nombre = nombre; }
 
  public float getPrecio () { return precio; }
  public void setPrecio (float precio) { this.precio = precio; }
}

Escribir ficheros XML

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
DOMImplementation dom = builder.getDOMImplementation();
 
//Creo un documento XML y creo el nodo raiz "productos"
Document documento = dom.createDocument(null, "xml", null);
Element raiz = documento.createElement("productos");
documento.getDocumentElement().appendChild(raiz);
 
Element nodoProducto = null; 
Element nodoDatos = null ;
Text texto = null;
 
//Recorro la lista de productos y creo un subnodo producto por cada elemento
for (Producto producto : listaProductos) {
  nodoProducto = documento.createElement("producto");
  raiz.appendChild(nodoProducto);
 
  //Por cada dato de cada producto creo un nuevo subnodo
  nodoDatos = documento.createElement("nombre");
  nodoProducto.appendChild(nodoDatos);
  texto = documento.createTextNode(producto.getNombre());
  nodoDatos.appendChild(texto);
 
  nodoDatos = documento.createElement("precio");
  nodoProducto.appendChild(nodoDatos);
  texto = documento.createTextNode(producto.getPrecio());
  nodoDatos.appendChild(texto);
}
 
//Finalizo el documento xml y lo guardo en un fichero de texto
Source src = new DOMSource(documento);
Result resultado = new StreamResult(new File("fichero.xml"));
 
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(src, resultado);

Leer ficheros XML

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document documento =  builder.parse(new File("fichero.xml"));
 
//Recorro cada uno de los elementos producto para obtener sus campos
NodeList productos = documento.getElementsByTagName("producto");
for (int i = 0; i < productos.getLength(); i++) {
  Node producto = productos.item( i );
  Element elemento = ( Element ) producto;
  System.out.println(elemento.getElementsByTagName("nombre").item(0).getChildNodes().item(0).getNodeValue());
  System.out.println(elemento.getElementsByTagName("precio").item(0).getChildNodes().item(0).getNodeValue());
}

Ficheros binarios

Producto.java
public class Producto implements Serializable {
  private static final long serialVersionUID = 1L;
  private String nombre;
  private float precio;
 
  public Producto(String nombre, float precio) {
    this.nombre = nombre;
    this.precio = precio;
  }
 
  public String getNombre() { return nombre; }
  public void setNombre(String nombre) { this.nombre = nombre; }
 
  public float getPrecio() { return precio ; }
  public void setPrecio(float precio) { this.precio = precio; }
}

Serialización de objetos

Serializar es el proceso por el cual un objeto en memoria pasa a transformarse en una estructura que pueda ser almacenada en un fichero (persistencia). Al proceso contrario le llamaremos deserializar.

Hay que tener en cuenta que durante el proceso de serialización, cada objeto se serializa a un fichero, por lo que si queremos almacenar todos los objetos de una aplicación, la idea más conveniente es tener todos éstos en alguna estructura compleja (y por comodidad dinámica) de forma que sea esta estructura la que serialicemos o deserialicemos para guardar o cargar los objetos de una aplicación. Estructuras como ArrayList, Set ó HashMap son clases muy utilizadas para trabajar de esta manera.

Interface Serializable

Para poder guardar objetos de una clase directamente en un fichero binario, todas las clases a las que pertenecen los objetos que guarde deben implementar Serializable. Esta interface no tiene métodos, por lo que no necesito sobrescribir nada al implementarla sobre una clase. La mayoría de las clases de la API de Java implementan Serializable.

Por ejemplo, si quiero guardar una lista de productos directamente en fichero, tanto la clase ArrayList, como la clase Producto, deben ser Serializable. ArrayList ya lo es, así que debemos implementarla sobre las clases que nosotros creamos (Producto en este caso).

Serializar un objeto

FileOutputStream flujoSalida = null;
ObjectOutputStream serializador = null;
try {
  flujoSalida = new FileOutputStream("archivo.dat");
  serializador = new ObjectOutputStream(flujoSalida);
 
  //Escribo el ArrayList completo
  serializador.writeObject(listaProductos);
 
} catch (IOException ioe) { 
  . . .
} finally {
  if (serializador != null)
    try {
      serializador.close();
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
  }

Deserializar un objeto

List<Producto> listaProductos = null;
 
FileInputStream flujoEntrada = null;
ObjectInputStream deserializador = null;
 
try {
  flujoEntrada = new FileInputStream("archivo.dat");
  deserializador = new ObjectInputStream(flujoEntrada);
 
  //Leo el ArrayList
  listaProductos = (ArrayList<Producto>)deserializador.readObject();
 
} catch (FileNotFoundException fnfe ) {
  fnfe.printStackTrace();
} catch (ClassNotFoundException cnfe ) { 
  cnfe.printStackTrace();
} catch (IOException ioe) { 
  ioe.printStackTrace();
} finally {
  if (deserializador != null)
  try {
    deserializador.close();
  } catch (IOException ioe) { 
    ioe.printStackTrace();
  }
}

Estructuras de datos

ArrayList

ArrayList es un array redimensionable que implementa el interface List. Permite todos los elementos, incluído el valor null. Como es redimensionable, dispone de método para modificar el tamaño del mismo.

Para el acceso concurrente con varios hilos, hay que tener en cuenta que esta estructura de datos no está sincronizada y su acceso deberá estarlo de forma externa. También se podría emplear la clase Vector que es igual que ésta pero está ya sincronizada.

Operaciones más comunes

  • Añadir un elemento
List<String> listaCadenas = new ArrayList<>();
String nuevaCadena = "Esto es una cadena";
 
listaCadenas.add(nuevaCadena);
  • Añadir un elemento en una posición determinada
int posicion = 3;
listaCadenas.add(3, nuevaCadena);
  • Reemplazar el elemento que ocupa una posición determinada
int posicion = 3;
String otraCadena = "Esto es otra cadena";
listaCadenas.set(3, otraCadena);
  • Eliminar un elemento
int posicion = 3;
listaCadenas.remove(posicion);
  • Obtener la referencia a un elemento
int posicion = 3;
String unaCadena = listaCadenas.get(posicion);
  • Conocer el número de elementos
System.out.println("El ArrayList tiene " + listaCadenas.size() + " elementos");
  • Eliminar un elemento concreto de la lista
listaCadenas.remove(objeto);
  • Eliminar todos los elementos de la lista
listaCadenas.clear();
  • Añadir todos los elementos de otra lista
List<String> otraListaCadenas = new ArrayList<>();
. . .
. . .
// Se puede pasar como parámetro un objeto que implemente el interfaz Collection
listaCadenas.addAll(otraListaCadenas);
  • Comprobar si esta vacía
if (listaCadenas.isEmpty()) {
  System.out.println("La lista no tiene elementos");
}
  • Obtener un array (estático) con todos los elementos de la lista
String[] arrayCadenas = listaCadenas.toArray();

HashMap

HashMap es una tabla hash que implementa el interface Map. Permite el valor null tanto para valores como para claves. Al contrario que TreeSet no garantiza el orden de los elementos o que éste se mantenga.

Hay que tener en cuenta que el acceso a esta implementación no está sincronizado, pero existe una implementación similar y sincronizada, que es la clase Hashtable.

Operaciones más comunes

  • Añadir un elemento (pareja clave-valor)
Map<String, Libro> libros = new HashMap<,>();
. . .
Libro libro = new Libro();
libro.setTitulo("Secuestrado");
libro.setAutor("Robert Louis Stevenson");
. . .
libros.add(libro.getTitulo(), libro);
  • Obtener un elemento
String tituloLibro = "Secuestrado";
Libro esteLibro = libros.get(titulo);
  • Comprobar si existe una clave
String titulo = "Secuestrado";
if (libros.containsKey(titulo)) {
  System.out.println("El libro con el título " + titulo + " existe en tu colección");
}
  • Comprobar si esta vacío
if (libros.isEmpty()) {
  System.out.println("Tu colección de libros está vacía");
}
  • Eliminar todos los elementos
libros.clear();
  • Eliminar un elemento (por clave)
String titulo = "Secuestrado";
libros.remove(titulo);
  • Obtener una Collection con todos los valores
Collection<Libro> coleccionLibros = libros.values();
// ArrayList, TreeSet y LinkedList implementan el interface Collection
  • Obtener un Set con todas las claves
Set<String> titulos = libros.keySet();
// TreeSet y HashSet implementan el interface Set
  • Comprobar el tamaño del Map
System.out.println("Tienes " + libros.size() + " libros en tu colección");
  • Concatenar todos los elementos de otro Map
Map<String, Libro> masLibros = new HashMap<,>();
. . .
. . .
libros.putAll(masLibros);

TreeSet

TreeSet es una colección que mantiene los elementos ordenados de forma natural o bien a través de un Comparator.

Operaciones más comunes

  • Añadir un elemento
Set<String> cadenas = new TreeSet<>();
String unaCadena = "Esto es una cadena";
cadenas.add(unaCadena);
  • Añadir todos los elementos de otra Collection
List<String> masCadenas = new ArrayList<>();
. . .
cadenas.addAll(masCadenas);
  • Obtener un elemento
  • Eliminar todos los elementos
cadenas.clear();
  • Comprueba si existe un elemento en la lista
String otraCadena = "Esto es una cadena";
if (cadenas.contains(otraCadena)) {
  System.out.println("La cadena existe");
}
  • Obtener el primer elemento
String primeraCadena = cadenas.first();
  • Obtener y eliminar el primer elemento
String primeraCadena = cadenas.pollFirst();
  • Obtener el último elemento
String ultimaCadena = cadenas.last();
  • Obtener y eliminar el último elemento
String ultimaCadena = cadenas.pollLast();
  • Comprobar si esta vacío
if (cadenas.isEmpty()) {
  System.out.println("El Set de cadenas esta vacío");
}
  • Comprobar el número de elementos
System.out.println("Tienes " + cadenas.size() + " cadenas");
  • Obtiene la parte del Set cuyos elementos son menores que uno pasado por parámetro
String cadenaLimite = "Una cadena";
SortedSet<String> cadenasMenores = cadenas.headSet(cadenaLimite);
// SortedSet es el interface que implementa TreeSet
  • Obtiene la parte del Set cuyos elementos están entre dos determinados
String cadenaMenor = "Una cadena";
String cadenaMayor = "Más cadena";
SortedSet<String> cadenasMenores = cadenas.subSet(cadenaMenor, cadenaMayor);
// SortedSet es el interface que implementa TreeSet

TreeMap

TreeMap es una colección de pares clave-valor como HashMap pero mantiene los elementos ordenados por su clave de forma natural o bien a través de un Comparator.

Operaciones más comunes

  • Añadir un elemento (pareja clave-valor)
Map<String, Libro> libros = new TreeMap<,>();
. . .
Libro libro = new Libro();
libro.setTitulo("Secuestrado");
libro.setAutor("Robert Louis Stevenson");
. . .
libros.put(libro.getTitulo(), libro);
  • Obtener un elemento
String tituloLibro = "Secuestrado";
Libro esteLibro = libros.get(titulo);
  • Comprobar si existe una clave
String titulo = "Secuestrado";
if (libros.containsKey(titulo)) {
  System.out.println("El libro con el título " + titulo + " existe en tu colección");
}
  • Comprobar si esta vacío
if (libros.isEmpty()) {
  System.out.println("Tu colección de libros está vacía");
}
  • Eliminar todos los elementos
libros.clear();
  • Eliminar un elemento (por clave)
String titulo = "Secuestrado";
libros.remove(titulo);
  • Obtener una Collection con todos los valores
Collection<Libro> coleccionLibros = libros.values();
// ArrayList, TreeSet y LinkedList implementan el interface Collection
  • Obtener un Set con todas las claves
Set<String> titulos = libros.keySet();
// TreeSet y HashSet implementan el interface Set
  • Comprobar el tamaño del Map
System.out.println("Tienes " + libros.size() + " libros en tu colección");
  • Concatenar todos los elementos de otro Map
Map<String, Libro> masLibros = new TreeMap<,>();
. . .
. . .
libros.putAll(masLibros);

LinkedList

LinkedList Es una lista doblemente enlazada que implementa el interfaz List

Operaciones más comunes

  • Añadir un elemento
List<Libro> listaLibros = new LinkedList<>();
. . .
Libro libro = new Libro();
libro.setTitulo("Secuestrado");
libro.setAutor("Robert Louis Stevenson");
. . .
listaLibros.add(libro);
  • Añadir un elemento al principio
List<Libro> listaLibros = new LinkedList<>();
. . .
Libro libro = new Libro();
libro.setTitulo("Secuestrado");
libro.setAutor("Robert Louis Stevenson");
. . .
listaLibros.addFirst(libro);
  • Añadir un elemento al final
List<Libro> listaLibros = new LinkedList<>();
. . .
Libro libro = new Libro();
libro.setTitulo("Secuestrado");
libro.setAutor("Robert Louis Stevenson");
. . .
listaLibros.addLast(libro);
  • Añadir toda una colección al final
List<Libro> listaLibros = new LinkedList<>();
List<Libro> otraListaDeLibros = new ArrayList<>();
. . .
// Podemos añadir cualquier objeto que implemente el interface Collection
listaLibros.addAll(otraListaDeLibros);
  • Obtener un elemento
Libro unLibro = listaLibros.get(4);
  • Obtener el primer elemento
Libro unLibro = listaLibros.getFirst();
  • Eliminar (y obtener) el primer elemento
Libro primerLibro = listaLibros.removeFirst();
  • Eliminar (y obtener) el último elemento
Libro ultimoLibro = listaLibros.removeLast();
  • Eliminar (y obtener) un elemento
Libro unLibro = listaLibros.remove(3);
  • Eliminar todos los elementos
listaLibros.clear();
  • Obtener el número de elementos de la lista
System.out.println("Esta lista tiene " + listaLibros.size() + " libros");
  • Obtener un array (estático) con todos los elementos de la lista
Libro[] arrayLibros = listaLibros.toArray();

Comparativa de la JFC (Java Collection Framework)

Patrón Modelo-Vista-Controlador

El patrón Modelo-Vista-Controlador o MVC (Model-View-Controller en inglés) es un patrón de diseño que busca separar, en una aplicación con GUI, la lógica de la presentación de los datos facilitando así la reutilización de código y el mantenimiento de la aplicación.

mvc.jpg

MVC: Model-View-Controller

Ejemplo de aplicación MVC

A continuación se muestra el código completo de una pequeña aplicación con GUI de gestión con las operaciones más básicas (Alta, Modificar, Baja, Búsqueda) gestionada mediante colecciones Java.

Según el patrón MVC, la aplicación quedará separada en tres partes: Model, View y Controller. A esas 3 partes habrá que añadir las clases POJOs (Plain Old Java Object) que son las clases que representan a los objetos o elementos que se gestionan en la aplicación. Contendrán atributos, constructores, getters, setters y los métodos que proceda implementar para cada clase. En este caso hay una única clase POJO que es Animal.java puesto que la aplicación permite gestionar una base de datos de animales, en este caso mediante colecciones Java de forma que queda como ejercicio muy interesante transformar esta aplicación en una que gestione la información con Bases de Datos (MySQL ó PostgreSQL). Será entonces cuando se aprecie la separación Model-View-Controller y la reutilización real de código entre ambas versiones de esta aplicación.

Animal.java
/**
 * Clase que representa a un Animal
 */
public class Animal {
 
  private String nombre;
  private String raza;
  private String caracteristicas;
  private float peso;
 
  // Constructores, getters y setters
  // Otros métodos (cuando proceda)
}

La clase Animal representa a los animales que vamos a gestionar y contendrá tantos atributos como características queramos almacenar del mismo. Además haremos los correspondientes constructores, getters y setters. Si procede, podemos añadir métodos que realicen operaciones sobre la clase.

Animal.java (Todo el código)
Ventana.java
/**
 * Clase que contiene la GUI de la Aplicación
 */
public class Ventana {
  private JPanel panel;
  JTextField tfNombre;
  JTextField tfRaza;
  // Resto de controles swing
  . . .
 
  public Ventana() {
 
    JFrame frame = new JFrame("Ventana");
    frame.setContentPane(panel);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

La clase Ventana contendrá exclusivamente el diseño de la ventana y en el constructor la inicialización de la misma según convenga en cada caso. Ni siquiera los Listeners aparecerán en esta ventana. Por comodidad, no utilizaremos ningún modificador de accesibilidad para los componentes de la ventana, puesto que deberemos poder acceder a ellos desde el Controller. Otra manera será proporcionar getters para todos ellos y así podremos declararlos como privados o lo que mejor convenga.

Ventana.java (Todo el código)
VentanaController.java
/**
 * Controlador para la ventana
 * Recibe los inputs del usuario desde la ventana (View) y se 
 * comunica con el Model si es necesario 
 */
public class VentanaController implements ActionListener, KeyListener {
 
  private VentanaModel model;
  private Ventana view;
 
  private int posicion;
 
  public VentanaController(VentanaModel model, Ventana view) {
    this.model = model;
    this.view = view;
    anadirActionListener(this);
    anadirKeyListener(this);
 
    posicion = 0;
  }
 
  @Override
  public void actionPerformed(ActionEvent event) {
 
    String actionCommand = event.getActionCommand();
    Animal animal = null;
 
    switch (actionCommand) {
      case "Nuevo":
         view.tfNombre.setText("");
         view.tfNombre.setEditable(true);
         view.tfCaracteristicas.setText("");
         view.tfCaracteristicas.setEditable(true);
         // Limpiar/Resetear resto de componentes
         . . .
         . . .
 
         view.btGuardar.setEnabled(true);
         break;
      case "Guardar":
         if (view.tfNombre.getText().equals("")) {
            Util.mensajeError("El nombre es un campo obligatorio", "Nuevo Animal");
            return;
         }
 
         animal = new Animal();
         animal.setNombre(view.tfNombre.getText());
         animal.setRaza(view.tfRaza.getText());
         animal.setCaracteristicas(view.tfCaracteristicas.getText());
         animal.setPeso(Float.parseFloat(view.tfPeso.getText()));
 
         model.guardar(animal);
 
         view.btGuardar.setEnabled(false);
         break;
      case "Modificar":
         animal = new Animal();
         animal.setNombre(view.tfNombre.getText());
         animal.setRaza(view.tfRaza.getText());
         animal.setCaracteristicas(view.tfCaracteristicas.getText());
         animal.setPeso(Float.parseFloat(view.tfPeso.getText()));
 
         model.modificar(animal);
         break;
      case "Cancelar":
         view.tfNombre.setEditable(false);
         view.tfCaracteristicas.setEditable(false);
         . . .
 
         animal = model.getActual();
         cargar(animal);
 
         view.btGuardar.setEnabled(false);
         break;
      case "Eliminar":
         if (JOptionPane.showConfirmDialog(null, "¿Está seguro?", "Eliminar", 
           JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION)
           return;
 
         model.eliminar();
         animal = model.getActual();
         cargar(animal);
         break;
      case "Buscar":
         animal = model.buscar(view.tfBusqueda.getText());
         if (animal == null) {
           Util.mensajeInformacion("No se ha encontrado ningún animal con ese nombre", 
             "Buscar");
           return;
         }
         cargar(animal);
         break;
      . . .
      . . .
      default:
         break;
      }
  }
 
  . . .
  . . .
 
  @Override
  public void keyReleased(KeyEvent e) {
    if (e.getSource() == view.tfBusqueda) {
      if (e.getKeyCode() == KeyEvent.VK_ENTER) {
        view.btBuscar.doClick();
      }
    }
  }
 
  /**
   * Carga los datos de un animal en la vista
   * @param animal
   */
  private void cargar(Animal animal) {
    if (animal == null)
      return;
 
    view.tfNombre.setText(animal.getNombre());
    view.tfCaracteristicas.setText(animal.getCaracteristicas());
    . . .
    . . .
  }
 
  . . .
  /**
   * Añade el Listener a todos los controles de la View
   */
  private void anadirActionListener(ActionListener listener) {
 
    view.btNuevo.addActionListener(listener);
    view.btGuardar.addActionListener(listener);
    . . .
    . . .
  }
}

La clase VentanaController contiene el Controller de la ventana, que hará de intermediario entre la capa View que es la GUI de la aplicación y el Model que contiene toda la lógica y el acceso a los datos. Además, se encarga de gestionar los eventos de cada componente de la GUI (implementando los Listeners necesarios).

También se incluyen en este componente todas las validaciones de datos relacionadas con el input del usuario y la visualización de los mensajes de errores que correspondan.

VentanaController.java (Todo el código)
VentanaModel.java
/**
 * Modelo para la ventana
 */
public class VentanaModel {
 
  private ArrayList<Animal> listaAnimales;
  private int posicion;
 
  public VentanaModel() {
    listaAnimales = new ArrayList<>();
    posicion = 0;
  }
 
  /**
   * Guarda un animal en la lista
   * @param animal
   */
  public void guardar(Animal animal) {
 
    listaAnimales.add(animal);
    posicion++;
  }
 
  /**
   * Modifica los datos del animal actual
   * @param animalModificado
   */
  public void modificar(Animal animalModificado) {
    Animal animal = listaAnimales.get(posicion);
    animal.setNombre(animalModificado.getNombre());
    animal.setCaracteristicas(animalModificado.getCaracteristicas());
    animal.setRaza(animalModificado.getRaza());
    animal.setPeso(animalModificado.getPeso());
  }
 
  /**
   * Elimina el animal actual
   */
  public void eliminar() {
    listaAnimales.remove(posicion);
  }
 
  public Animal getActual() {
    return listaAnimales.get(posicion);
  }
 
  /**
   * Busca un animal en la lista
   * @param nombre El nombre del animal
   * @return El animal o null si no se ha encontrado nada
   */
  public Animal buscar(String nombre) {
    for (Animal animal : listaAnimales) {
      if (animal.getNombre().equals(nombre)) {
        return animal;
      }
    }
 
    return null;
  }
 
  /**
   * Obtiene el animal que está en primera posición en la lista
   * @return
   */
  public Animal getPrimero() {
    posicion = 0;
    return listaAnimales.get(posicion);
  }
 
  /**
   * Obtiene el animal que está en la posición anterior a la actual
   * @return
   */
  public Animal getAnterior() {
    if (posicion == 0)
      return null;
 
    posicion--;
    return listaAnimales.get(posicion);
  }
 
  /**
   * Obtiene el animal que está en la posición siguiente a la actual
   * @return
   */
  public Animal getSiguiente() {
 
    if (posicion == listaAnimales.size() - 1)
      return null;
 
    posicion++;
    return listaAnimales.get(posicion);
  }
 
  /**
   * Obtiene el animal que está en la última posición de la lista
   * @return
   */
  public Animal getUltimo() {
 
    posicion = listaAnimales.size() - 1;
    return listaAnimales.get(posicion);
  }
}

La clase VentanaModel, puesto que es el Model, contendrá todos los métodos que proporcionan el acceso a los datos y la lógica de la aplicación. En este caso contiene todos los métodos que permite obtener la información de los animales, los resultados de las búsquedas y también se llevan a cabo las operaciones de inserción, modificación y eliminación.

En este caso todas las operaciones se realizan sobre una colección ArrayList. Si quisiéramos hacer que la aplicación utilizara una Base de Datos como motor de almacenamiento sólo tendríamos que modificar este componente de la aplicación haciendo que los mismos métodos (incluso respetando su declaración: nombre, tipo devuelto y parámetros) utilicen una Base de Datos para realizar todas las operaciones. No sería necesario realizar ninguna modificación en el resto de la aplicación.

VentanaModel (Todo el código)
Aplicacion.java
/**
 * Aplicación para la gestión de animales utilizando el patrón MVC
 *
 * Clase principal, que solamente lanza la aplicación
 */
public class Aplicacion {
 
  public static void main(String args[]) {
 
    Ventana view = new Ventana();
    VentanaModel model = new VentanaModel();
    VentanaController controller = new VentanaController(model, view);
  }
}

En la clase Aplicacion, que contiene el método main que lanzará la aplicación, simplemente tenemos que instanciar los tres componentes de nuestra aplicación (Model, View y Controller) y “conectarlos” mediante el último.


Ejercicios

  1. Escribe un programa capaz de registrar información sobre los alumnos de un Colegio (nombre, apellidos, fecha de nacimiento y ciclo que estudian) y listarla en un JList. La información que se muestra en la lista se mantendrá actualizada en todo momento. Diseña la aplicación según el patrón de diseño MVC (Model-View-Controller)
  2. Añade al programa anterior los botones necesarios para poder “navegar” (ir al primero, anterior, siguiente y último) por los registros de la lista

  3. Añadir un botón al primer ejercicio para que el usuario guarde la información de la lista en un fichero. El usuario tendrá que escoger cada vez el fichero donde quiere almacenar la información

  4. Crea una aplicación que sea capaz de exportar a XML la información de una receta (nombre, descripción, ingredientes)
  5. Crea una aplicación que sea capaz de importar un fichero XML con información de un catálogo de películas (titulo, fecha, genero, sinopsis y actores principales). Diseña la GUI para que se pueda visualizar toda la información de cada una de las películas importadas
  6. Crea una aplicación que sirva de Bloc de Notas con las funciones de Nuevo, Guardar y Guardar como. Diseña la aplicación según el patrón de diseño MVC (Model-View-Controller)

Proyectos de ejemplo

Los proyectos de ejemplo con contenidos de esta unidad están disponibles en el repositorio de proyectos UD1.

Para manejaros con Git recordad que tenéis una serie de videotutoriales en la sección Referencias

Por otra parte, para el tema de creación y uso de distintos cuadros de diálogo, podeis consultar la sección de los apuntes de programación. También los apuntes sobre funcionamiento de la mayoría de componentes de las librerías de Swing.

También, los proyectos de ejemplo de estos apuntes están en el repositorio java-ficheros de BitBucket del profesor Santiago Faci. Podéis acceder tanto al código como a las descargas, a continuación:

Prácticas

  • Práctica 1.1 Desarrollar una aplicación que almacene datos en un fichero

© 2021 Santiago Faci y Fernando Valdeón

apuntes/ficheros.txt · Última modificación: 2020/09/30 09:15 (editor externo)