Buscar
Social
Ofertas laborales ES
martes
nov172009

Curso de programación Java III - Abraham Otero

Curso de programación Java III

 

 Artículo publicado originalmente en la revista Sólo Programadores 

 

Hasta ahora en este curso de programación Java hemos introducido los tipos de datos primitivos del lenguaje (los que permiten representar números enteros, números reales y valores lógicos); hemos presentado algunos tipos de datos más complejos (Strings, arrays y enumeraciones); y hemos revisado las distintas estructuras de control de flujo (bucles y condicionales) que proporciona el lenguaje. Estas herramientas, junto con las funciones y los módulos, son los pilares de la programación estructurada.

 

Sin embargo, Java no es un lenguaje de programación estructurado. Es un lenguaje de programación orientado a objetos. En Java un programa no es un código que llama a procedimientos o funciones; un programa es un montón de objetos, independientes entre si, que dialogan entre ellos pasándose mensajes para llegar a resolver el problema en cuestión. 

 

Esta serie de artículos no presentará en profundidad la programación orientada a objetos (POO). El abordar esta tarea nos llevaría a escribir otra serie de artículos completa. Sí introduciremos brevemente algunos conceptos de la POO, y daremos alguna justificación de su necesidad. Pero donde haremos más énfasis será en el soporte que el lenguaje de programación Java proporciona para la POO. Si bien será posible seguir la serie de artículos con los conceptos de orientación a objetos que se introducirán en este artículo, recomiendo al lector que no tenga experiencia en este campo que lea alguno de los muchos libros publicados al respecto o, en su defecto, el tutorial Orientación a Objetos: Conceptos y Terminología.

 

En este tercer capítulo de la serie, será el momento de comenzar a usar un entorno de desarrollo. Hasta ahora, si el lector ha seguido mi consejo, habrá estado usando el jdk en la consola. El entorno de desarrollo que usaremos para este capítulo será BlueJ (http://bluej.org, ver figura 1). No se trata de un entorno de desarrollo profesional, sino de un entorno de desarrollo orientado a la docencia y diseñado para presentar conceptos relativos a la POO. Si ya conoces en profundidad la POO puedes decidir saltar directamente a Netbeans o Eclipse. No obstante, este artículo está escrito pensando en aquellos lectores con pocos o nulos conocimientos de POO.

 

 

FIGURA 1: En la web de BlueJ además de descargar el IDE podréis encontrar documentación sobre él

 

En el artículo no tengo intención de explicar cómo usar BlueJ; lo cual no quiere decir que su uso sea trivial para alguien que desconozca la POO. Simplemente resulta que un medio impreso no es lo ideal para presentar el uso de una herramienta de escritorio. En el CD de la revista podrás encontrar un tutorial flash donde se explica cómo trabajar con BlueJ. En el artículo indicaré cuándo es el momento ideal para ver dicho tutorial.

 

 

1 La abstracción en los lenguajes de programación: el porqué de la programación orientada a objetos

 

La abstracción es una herramienta que nos permite comprender, analizar y resolver problemas complejos del mundo real de un modo eficaz. La abstracción se fundamenta en eliminar todo aquello que no es relevante para un problema permitiendo centrarnos sobre aquella información que sí lo es; el ser o no relevante depende fuertemente del contexto del problema.

 

Aunque la abstracción es una herramienta general aplicable a cualquier campo, a nosotros nos interesa la abstracción que proporciona un lenguaje de programación. Todos los lenguajes de programación proveen abstracciones. Muchos autores defienden que la complejidad de los problemas que un lenguaje de programación permite resolver depende del tipo y de la calidad de las abstracciones que el lenguaje permite crear.

 

El lenguaje ensamblador proporciona una pequeña abstracción respecto a la máquina sobre la que se ejecuta el programa. Los lenguajes de programación imperativos (Fortran, BASIC, Pascal, C, etc.) son abstracciones del lenguaje ensamblador. Así, por ejemplo, estos lenguajes nos permiten sumar dos números cualesquiera con el operador "+". Las operaciones que hay que realizar para sumar dos enteros de 32 bits son diferentes de las que hay que hacer para sumar dos enteros de 64 bits. Y no tienen nada que ver con las operaciones que hay que hacer para sumar dos números reales de 32 bits, números que se representan de un modo completamente diferente en el ordenador. Nuevamente, la suma de números reales de 64 bits difiere de la suma de números reales de 32 bits. Sin embargo estos lenguajes de programación nos permiten olvidarnos (abstraernos) de los detalles de cómo se realiza la operación de suma, y centrarnos en su semántica: la adición de dos números.

 

 Si bien estos lenguajes son una gran aportación respecto al ensamblador, no son suficiente, ya que siguen requiriendo que el programador piense en términos de la estructura del ordenador más que en términos del problema que tienen que resolver. El programador se ve obligado a establecer una asociación entre el modelo de la máquina (que, por ejemplo, comprende variables enteras, reales, cadena de caracteres, instrucciones condicionales, bucles, etc.) y el modelo del problema que pretende resolver (que, por ejemplo, puede comprender cuentas bancarias con sus saldos y sus respectivos propietarios, intereses se deben de abonar en cada cuenta, comisiones que se deben de retirar, condiciones de cobro de hipotecas, etc.). El esfuerzo que requiere al programador realizar una correspondencia entre ambos mundos hace que los programas sean difíciles de escribir y de mantener y ha sido el detonante para que aparezcan una gran cantidad de metodologías de programación en la industria del software.

 

 

FIGURA 2: Establecer una asociación adecuada entre el modelo de la máquina y el modelo del mundo real es una de las tareas más complicadas de la programación

 

Una forma de aumentar el nivel de abstracción de los lenguajes de programación sería diseñar un lenguaje de muy alto nivel que incorporase múltiples estructuras de datos generales y un conjunto de operaciones para manejar dichas estructuras. Estos lenguajes tratan de modelar el problema a resolver y no la máquina en la que se ejecutará el programa. Así, por ejemplo, en Lisp y APL se modela una visión particular del mundo: para el primero, todos los problemas se traducen en listas; para el segundo, en algoritmos. PROLOG, quizás el lenguaje de más difusión que sigue esta aproximación, modela todos los problemas como cadenas de decisiones. Todos estos lenguajes son una buena aproximación para resolver un tipo particular de problemas: aquéllos para los cuales aportan unas estructuras de datos y operaciones que componen una abstracción adecuada del problema. Sin embargo, cuando abandonamos el dominio para el cual han sido creados se convierten en terriblemente torpes y engorrosos, ya que no proporcionan las abstracciones adecuadas.

 

Intentar construir un lenguaje que modele la mayor parte de las abstracciones del mundo real que sus usuarios pudiesen demandar ha resultado ser, al menos hasta la fecha, un problema inabordable. La gran cantidad de estructuras de datos y de operaciones entre ellas que habría que contemplar convierten al problema del diseño de este lenguaje en intratable.

 

Llegados a este punto, la mejor solución al problema que se nos plantea es obvia: ya que no podemos proporcionar todas las abstracciones que cualquier usuario puede requerir para resolver cualquier problema, proporcionaremos la mejor herramienta posible (lenguaje de programación) para que el usuario construya las abstracciones que le permitan modelar el problema de un modo lo más adecuado posible. Después, usando las abstracciones que ha creado, el programador resuelve el problema. Ésta es la aproximación seguida por los lenguajes de programación orientada objetos: tratan de proporcionar herramientas generales (que no limiten al programador a un determinado dominio) para representar los elementos que componen el problema. Estos elementos que componen el problema se denominan "objetos". Por tanto, la idea es que el lenguaje se adapte a cada problema particular creando las abstracciones más adecuadas para él.

 

No obstante, esta aproximación no es incompatible con la anterior: los lenguajes de programación orientados a objetos, en general, y Java, en particular, ofrecen en sus librerías un conjunto cada vez más amplio de estructuras de datos ya implementadas y listas para ser empleadas por el programador. Sin embargo, la clave de su potencia es que permiten al programador definir nuevas estructuras extendiendo las ya existentes y/o modificándolas, o bien partiendo desde cero cuando las librerías del lenguaje no le proporcionan ninguna herramienta útil en la que apoyarse.

 

 

2 Objetos y clases en Java

 

Como ya hemos dicho, en POO un programa consta de un conjunto de objetos que intercambian entre ellos mensajes para resolver un determinado problema. A un objeto no le debe importar en absoluto cómo está implementado otro objeto, qué código tiene o deja de tener, qué variables usa... sólo le importa a qué mensajes es capaz de responder. Un mensaje es la invocación de un método de otro objeto. Un método es muy semejante a un procedimiento de la programación estructurada: a un método se le pasan uno, varios o ningún parámetros y nos devuelve un dato a cambio. Sin embargo, los métodos están asociados a los objetos (y por tanto sólo se pueden invocar sobre un objeto) y pueden realizar funciones diferentes dependiendo del estado en el que se encuentre el objeto.

 

Si analizamos lo que hemos dicho en el párrafo anterior veremos que los objetos parecen tener dos partes bastante diferenciadas: una es la parte que gestiona los mensajes, que ha de ser conocida por los demás, y que no podremos cambiar en el futuro sin modificar los demás objetos. La otra parte es el mecanismo por el cual se generan las acciones requeridas por los mensajes y el conjunto de variables que se emplean para llevar a cabo estas acciones. Esta segunda parte es, en principio, totalmente desconocida para los demás objetos. Por ello podemos en cualquier momento modificarla sin que a los demás les importe, y además cada programador tendrá total libertad para llevarla a cabo como él considere oportuno.

 

¿Cómo hago para programar objetos en Java? Los objetos no son programados directamente; lo que el programador debe hacer es construir clases. Una vez ha construido las clases, puede crear objetos a partir de ellas. Una clase es una descripción de un conjunto de objetos que manifiestan los mismos atributos, operaciones, relaciones y la misma semántica. Una clase puede verse desde tres perspectivas diferentes pero, a la vez, complementarias:

 

 

  • Como un conjunto de objetos que comparten una estructura y un comportamiento.
  • Como una plantilla que permite crear objetos.
  • Como la definición de la estructura y del comportamiento de un conjunto de objetos.

 

 

La primera perspectiva hace énfasis en la clasificación y en la abstracción. Una clase es una abstracción software de un conjunto de objetos (que se pueden corresponder con objetos del mundo real o no) que, por compartir una serie de atributos y comportamiento, clasificamos bajo una misma etiqueta. La segunda perspectiva toma un enfoque un tanto utilitarista: la clase es la "herramienta" que los lenguajes de programación emplean para construir objetos. La tercera hace énfasis en que la definición de una clase es una estructura común que se puede reutilizar (crear objetos con ella) cuántas veces se quiera. A la acción de crear un objeto de una clase se la denomina instanciar; y a los objetos creados instancias de la clase.

 

Basta ya de teoría; veamos cuál es la sintaxis de Java para definir clases.

 

2.1 Definición de clases en Java

 

La forma más general para definir una clase en Java es empleando la siguiente sintaxis:

 

[Modificador] class NombreClase [extends NombreClasePadre] [implements interface] {
Declaración de variables;
Declaración de métodos;
}

 

Los campos que van entre corchetes no son obligatorios. NombreClase es el nombre que le queramos dar a nuestra clase, NombreClasePadre es el nombre de la clase padre, de la cual hereda los métodos y variables, e interface es una especie de "contrato" que la clase se compromete a cumplir. La herencia y las interfaces serán abordadas en detalle más adelante. Como ambos campos son opcionales, por lo de ahora los ignoraremos. 

 

Los modificadores indican distintas características de la clase. Veamos qué opciones tenemos: 

 

 

  • public: la clase es pública y por lo tanto accesible para todo el mundo. Sólo podemos tener una clase pública por unidad de compilación, es decir, por cada fichero de código fuente.
  • Ninguno: la clase es “amistosa”. Será accesible para las demás clases del package (paquete). Los paquetes serán introducidos en el siguiente artículo de esta serie. Son una estructura que permite organizar programas relativamente complejos donde entran en juego un número de clases alto. Como este es nuestro primer artículo sobre orientación a objetos en Java, los programas que haremos son sencillos y no tomaran ventaja del uso de paquetes. Por otro lado, mientras todas las clases con las que estemos trabajando estén en el mismo directorio pertenecerán al mismo paquete y, por tanto, no emplear modificador es equivalente a emplear el modificador  public . Como por lo de ahora trabajaremos en un solo directorio asumiremos que la ausencia de modificador es equivalente a que la clase sea pública.
  • final:  indicará que esta clase no puede “tener hijos”, no se puede derivar ninguna clase de ella.
  • abstract: se trata de una clase de la cual no se puede instanciar ningún objeto. Habitualmente, las clases abstractas se suelen corresponder con objetos abstractos del mundo real. Por ejemplo, con el concepto de "ser vivo". En el mundo real no hay seres vivos "puros"; hay perros, gatos, palmeras, algas, peces... y todos ellos son "seres vivos". Pero no existe una entidad "ser vivo" que no pertenezca a alguna categoría de animal o planta más específica. El concepto de "ser vivo" en si mismo es un concepto abstracto.

 

 

Cuando presentemos la herencia de clases en Java los modificadores  final  y  abstract  cobrarán más sentido. Ya sé que puede parecer que me esté dejando muchas cosas en el tintero y que así va a ser muy difícil seguir la explicación. No es simple introducir la POO y, a pesar de que llevo casi 10 años explicándola todos los años en cursos universitarios (tanto en Java como en C++) todavía no he encontrado una forma de presentarla sin introducir algunos conceptos antes de explicarlos en profundidad. Ten un poco de fe y sigue leyendo. Las cosas irán encajando y todo comenzará a tener sentido más adelante.

 

El listado 1 muestra un ejemplo de clase Java. Nuestra clase representaría a una persona; la persona tendría dos atributos: una edad y un nombre. Dentro de la clase hemos definido tres métodos, que simplemente muestran por consola o bien un texto o bien el valor de uno de los atributos de la clase.

 

Como puede observarse en el listado 1, Java admite lo que se llama sobrecarga de métodos: puede haber varios métodos con el mismo nombre pero a los cuales se les pasan distintos parámetros. Según los parámetros que se le pasen se invocará a uno u otro método.

 

Si el lector lo desea, puede compilar el código del listado 1. No le será posible, sin embargo, ejecutarlo. No tiene un método  main.

 

 

//LISTADO 1: Clase que representa a una persona.
//código Persona.java del CD
public class Persona {
    private int edad;
    private    String nombre;
 
    public void nace(){
        System.out.println("Hola mundo");
    }
    public   void getNombre(){
        System.out.println(nombre);
    }
    public void getNombre(int i){
        System.out.println(nombre +" " +i);
    }
     public void getEdad(){
        System.out.println(edad);
}

 

 

2.1.1 Constructores

 

Los constructores se definen de un modo muy similar a los métodos, pero su nombre debe coincidir con el nombre de la clase y nunca devuelven ningún tipo de dato, no siendo necesario indicar que el tipo de dato devuelto es  void . Dentro de un constructor suele haber código para inicializar los valores de los objetos y realizar las operaciones que sean necesarias para la generación de este objeto (crear otros objetos que puedan estar contenidos dentro de este objeto, abrir un archivo o una conexión de internet...). Como veremos más adelante, la utilidad de los constructores es permitir al programador crear objetos a partir de la clase: para crear un objeto deberemos invocar uno de los constructores. Al igual que sucede con los métodos, los constructores admiten sobrecarga. 

 

En el listado 2 podemos ver dos constructores de la clase Persona. El primero, el que no toma parámetros, se llama constructor por defecto. Si cuando creamos una clase no indicamos ningún constructor, el compilador generará un constructor por defecto para la clase de modo automático. Nuestro constructor por defecto nos permite crear una persona que se llama "Paco" y que tiene 30 años. El segundo constructor permite especificar la edad y el nombre de la persona que se va a construir.

 

this  es una variable especial de sólo lectura que proporciona Java. Contiene una referencia al objeto en el que se usa dicha variable. A veces es útil que un objeto pueda referenciarse a si mismo; en el listado 2 esto se emplea para distinguir entre la variable  edad  argumento de uno de los constructores de Persona del atributo  edad  ( this.edad ) de la clase. 

 

 

//LISTADO 2: Constructores de la clase Persona
//código Persona.java del CD
    public Persona(){
         edad = 30;
         nombre = "Paco";
    }
    public Persona(int edad, String nombre){
         this.edad = edad;
         this.nombre = nombre;
    } 

 

 

Otro uso de  this , como se muestra en el listado 3, es invocar a un constructor de la clase desde otro constructor. En este caso, la llamada al constructor debe ser la primera sentencia dentro del constructor que invoca al segundo.

 

 
//LISTADO 3: Uso de this para invocar desde un constructor a otro
class Cliente{
    public Cliente(String n){
//Llamamos al otro constructor.
        this(n, Cuenta.nuevo_numero());
        .....
    }
    public Cliente (String n, int a){
        nombre = n;
        numero_cuenta = a;
    }
    .....
 

 

Este es un buen momento para echarle un vistazo al tutorial flash que podrás encontrar en el CD de la revista. En él, tras realizar una breve introducción a BlueJ, se carga esta clase, se crean varios objetos de ella y se muestra cómo invocar sus métodos. Esto es posible sólo mediante BlueJ, y no mediante el JDK. Para poder emplear esta clase con el JDK de Sun tendríamos que escribir un programa Java que crease instancias de la clase e invocase sus métodos. BlueJ permite realizar estas acciones empleando menús contextuales (ver figura 3), lo que nos permite "jugar" con la clase de un modo similar a como los intérpretes de lenguajes de programación como Python permiten crear instancias e invocar métodos de los objetos creados. Según mi experiencia, esto resulta muy útil para familiarizarse con los conceptos de la POO.

 

  

FIGURA 3: BlueJ permite crear instancias de clases Java e invocar a los métodos de las instancias

 

 

2.1.2 Modificadores de métodos y variables

 

Aunque hasta que expliquemos la herencia y los paquetes algunos de los modificadores que vamos a ver a continuación carecerán de sentido, por completitud los presentaremos aquí. El primer tipo de modificadores que vamos a ver son los modificadores de visibilidad; éstos permiten indicar quién podrá ver y acceder a un método o un atributo de una clase. Éstos son los modificadores que debemos emplear para determinar cuál es la parte pública de nuestros objetos (la parte que va a ser accesible por los demás) y cuál es la parte privada (la parte que son detalles de implementación y que no queremos exponer). Estos modificadores son los mismos tanto para variables como para métodos y son:

 

 

  • public: puede acceder todo el mundo a la variable o método. Por norma general, las variables nunca son públicas; las variables siempre suelen considerarse detalles de implementación. La parte pública de una clase suele estar constituida por métodos y constructores.
  • Ninguno: “amistosa”, el atributo o método es accesible por cualquier miembro del paquete (package), pero no por otras clases que pertenezcan a otro paquete distinto. Más adelante explicaremos que es un paquete.
  • protected: el atributo o método es accesible por las clases hijas de la clase que posee la variable y por las que estén en el mismo paquete. El significado de este modificador tendrá más sentido cuando se explique la herencia.
  • private: nadie salvo la clase misma puede acceder a estas variables o métodos. Debemos tener en cuenta que podrán acceder a ellos todas las instancias de la clase. Por norma general, todas las variables de la clase deben ser privadas. También es frecuente que en una clase haya métodos privados, métodos que suelen ser auxiliares para otros métodos públicos que componen la interfaz de la clase pero cuya funcionalidad no se quiere exponer.

 

 

Mientras no introduzcamos la herencia y los paquetes, los únicos modificadores que tienen sentido son  public  y  private . Emplear el primero significa colocar el método o la variable en la interfaz de la clase y hacerlo accesible a otras clases. Emplear el segundo significa ocultarlo.

 

Los cuatro modificadores que hemos presentado son mutuamente excluyentes; es decir, un atributo (obviamente) no puede ser  public  y  private  a la vez. Los modificadores que presentaremos a continuación no son excluyentes ni entre sí, ni respecto a los cuatro anteriores. Aunque son los mismos tanto para los atributos como para los métodos, su semántica es diferente por lo que los presentaremos por separado.

 

En el caso de los atributos, además del modificador de visibilidad, podemos especificar los modificadores:

 

 

  • static: la variable atributo es la misma para todas las instancias de una clase: todas comparten ese dato. Si una instancia lo modifica todas ven dicha modificación. Las variables estáticas son equivalentes a las variables de la programación estructurada: definir la variable estática significa tener una única variable estática, mientras que definir la variable no estática significa tener una variable por cada objeto que se cree a partir de la clase.
  • final: se emplea para definir constantes: un dato tipo final no puede variar nunca su valor. La variable no tiene porque inicializarse en el momento de definirse, pero cuando se inicializa ya no puede cambiar su valor.

 

 

En el listado 4 vemos un ejemplo de uso de ambos modificadores. Por un lado, se emplea el modificador final para definir una cadena de caracteres cuyo valor nunca va a cambiar. Por otro lado, se emplea una variable estática para llevar cuenta del número de instancias que se han creado de la clase. La variable  numeroMarcianos  se incrementa cada vez que se crea un  Marciano , y se decrementa cada vez que se muere un  Marciano . Como es una variable estática, todas las instancias de la clase comparten la misma variable, por lo que cuando una instancia la incrementa todas ven el valor incrementado y cuando una instancia la decrementa todas ven el valor decrementado.

 

El tutorial flash muestra cómo emplear BlueJ para demostrar cómo funcionan las variables estáticas.

 

 
//LISTADO 4: Ejemplo de uso de variables estáticas y finales.
//código Marciano.java del CD
class Marciano {
    private boolean vivo;
    private static int numeroMarcianos = 0;
    final String soy = "marciano";
 
    Marciano(){
        vivo = true;
        numeroMarcianos++;
    }
    void muerto(){
        if(vivo){
            vivo = false;
            numeroMarcianos--;
        }
    }
    void quienEres(){
        System.out.println("Soy un " + soy);
    }
}

 

 

Los métodos también pueden llevar el modificador  static  o  final , aunque en este caso su significado es distinto:

 

 

  • static: es un método al cual se puede invocar sin crear ningún objeto de dicha clase.  Math.sin()  y  Math.cos()  son dos ejemplos de métodos estáticos. Los métodos estáticos son equivalentes a las funciones de la programación estructurada. Desde un método estático sólo podemos invocar otros métodos que también sean estáticos, y sólo podemos acceder a atributos de la clase que sean estáticos.
  • final: si un método es final no podrá ser cambiado por ninguna clase que herede de la clase donde se definió. Es un método que no se puede sobrescribir. Nuevamente, cuando se presente la herencia se explicará este punto con más detalle.

 

 

Definir un método como estático es hacerlo equivalente a una función de la programación estructurada. Todos los métodos de la clase  Math  son estáticos, de ahí que se puedan invocar directamente sin necesidad de crear instancias. Para invocar a un método estático antes del nombre del método debe ir en nombre de la clase, y entre ambos un ".". En el listado 5 vemos una clase  Mat  con dos métodos que permiten realizar operaciones matemáticas, de un modo similar a la clase  Math . Estos métodos son invocados desde el método  main . 

 

  

 FIGURA 4: BlueJ permite invocar directamente los métodos estáticos sobre las clases

 

 

El método  main  siempre tiene que ser estático porque es el punto de entrada del programa: si el método  main  no fuese estático, antes de invocarlo tendríamos que crear un objeto de la clase que lo contiene. Pero como el programa no se ha comenzado ejecutar todavía (el  main  es lo primero que se ejecuta) no hay nadie que pueda crear ese objeto. De ahí que tenga que ser estático.

 

 
//LISTADO 5: Ejemplo de uso de varios métodos estáticos.
//código Mat.java del CD
class Mat{
   public static int cuadrado(int i){
        return i*i;
    }
   public static int mitad (int i){
        return i/2;
    }
  public static void main(String[] args) {
    System.out.println("Mat.cuadrado (40): " + Mat.cuadrado (40));
    System.out.println("Mat.mitad (40): " + Mat.mitad (40));
    }
 

 

 

2.2 Creación y referencia a objetos

 

El propósito de las clases es permitirnos crear objetos. Un objeto en el ordenador es esencialmente un bloque de memoria con espacio para guardar las variables de dicho objeto. En Java todos los objetos se crean en el heap o memoria dinámica y para crearlos se emplea el operador  new . Si quisiéramos crear un objeto de tipo persona podríamos emplear las siguientes sentencias:

 

Persona hombre;
hombre = new Persona ();

 

Con la primera sentencia hemos creado una variable capaz de referenciar un objeto de tipo Persona. Con la segunda inicializamos la variable, de tal forma que contenga una persona que se llamará "Paco" y tendrá 30 años, ya que esto es lo que hacía el constructor por defecto de esta clase. Si quisiésemos indicar el nombre y la edad de la persona que estamos creando podríamos invocar al otro constructor de la clase:

 

Persona pepe = new Persona ("Pepe", 45);

 

En este caso habremos construido un objeto de tipo persona cuyo nombre es "Pepe" y que tiene 45 años. Una vez hemos construido un objeto podemos acceder a sus atributos y métodos empleando el operador ".", siempre que el código que está intentando realizar el acceso tenga permiso para ello. Estos permisos dependerán de los modificadores de visibilidad que tengan dichos atributos o métodos. Así, por ejemplo, si el modificador de visibilidad es  public  cualquier código podrá acceder al atributo o método. Si el modificador de visibilidad es  private , sólo el código de la propia clase podrá hacer era dicho tributo o método.

 

Dado nuestro objeto  pepe , podremos obtener su nombre y su edad, respectivamente, invocando los correspondientes métodos de la clase  Persona :

 

int edadPepe = pepe.getEdad ();
int nombrePepe = pepe.getNombre ();

 

Las dos sentencias anteriores son válidas en cualquier parte de nuestro programa, ya que ambos métodos son  public . Sin embargo las sentencias:

 

int edadPepe = pepe.edad;
int nombrePepe = pepe.nombre;

 

que acceden directamente a los atributos de la clase  Persona  sólo serán válidas dentro del código de la propia clase  Persona , ya que ambos atributos eran  private.

 

 

2.3 La herencia

 

La herencia permite que una clase pueda basarse en otra ya existente para construirse; constituye, por tanto, un mecanismo muy potente de reutilización de código. Mediante ella una clase, denominada normalmente clase hija, clase derivada o subclase, hereda propiedades y comportamiento de otra clase, denominada clase padre, clase base o superclase. La herencia organiza jerárquicamente las clases que se necesitan para resolver un problema.

 

La herencia se apoya en el significado de ese concepto de la vida diaria. Así, los seres vivos se dividen en animales y plantas; los animales, a su vez, en mamíferos, anfibios, insectos, aves, reptiles... los mamíferos a su vez en terrestres y acuáticos... cada una de estas clases o categorías tiene un conjunto de atributos y comportamiento común a todos los miembros de la clase: todas las hembras de los mamíferos poseen glándulas mamarias que sirven para alimentar a sus crías cuando nacen. Estas propiedades y comportamiento son heredadas por todas las subclases de una clase: lo dicho antes para los mamíferos es igualmente válido para los perros, ya que los perros son mamíferos (pertenecen a la clase de los mamíferos).

 

 

FIGURA 5: El concepto de herencia en los lenguajes de programación es similar al concepto de herencia del mundo real

 

Una clase hija especializa y extiende el comportamiento de la clase padre; así, un perro es un mamífero, exactamente igual que la ballena; sin embargo un perro también tiene pelo y cuatro patas, cosa que ya no sucede con todos los mamíferos. Podemos decir que un perro es "un tipo más especializado/concreto" de mamífero, que extiende las propiedades y el comportamiento de un mamífero incorporando pelo, cuatro patas, el alimentarse de carne y el ladrar, entre otros.

 

Una forma de identificar relaciones de tipo herencia es buscando en la descripción del problema las palabras "es un": un perro es un mamífero, un mamífero es un ser vivo, etcétera. El sustantivo que va antes del "es un" se corresponderá con una clase hija y el que va después con la clase padre. No debe confundirse la herencia con la composición. Ambos son mecanismos para reutilizar software disponibles en los lenguajes de POO; sin embargo, mientras que la herencia es un mecanismo propio de la programación orientado a objetos la composición ya se empleaba en la programación estructurada. La composición se suele corresponder con las palabras "tiene un" de la descripción de un problema: un equipo de fútbol tiene un entrenador; un equipo de fútbol tiene varios jugadores; una cuenta bancaria tiene un saldo, etcétera.

 

2.3.1 Sintaxis de la herencia en Java 

 

En Java para indicar que una clase hereda de otras se emplea la palabra reservada  extends , la sintaxis es:

 

[Modificador] class ClaseHija extends ClasePadre {...}

 

Supongamos que tenemos la clase que se muestra en el listado 6. Esta clase representa un ser vivo. Nuestra clase  Persona  es un ser vivo. Y, por tanto, además de tener una edad y un nombre debería tener la funcionalidad correspondiente con ser vivo (estar vivo, y poder morirse en algún momento). Ese código nos resulta útil. Por tanto podemos reutilizarlo mediante la herencia; para ello simplemente indicamos que nuestra clase hereda de la clase SerVivo. Para ello declararemos la clase como sigue:

 

public class Persona extends SerVivo

 

Con sólo cambiar esa declaración habremos heredado los dos métodos de la clase padre y sus atributos. 

 

 
//LISTADO 6: Clase SerVivo de la cual hereda Persona
//código SerVivo.java del CD
public class SerVivo{
    private boolean vivo;
 
    public SerVivo(){
         vivo = true;
    }
 
    public void morir(){
        vivo = false;
    }
    public   boolean isVivo(){
        return vivo;
    }
}
 

 

Si un método de la clase padre no hace lo que nosotros necesitamos podemos sobrescribirlo (override). Para ello basta con volver a definir en la clase hija un método con el mismo nombre que el método de la clase padre. En ocasiones, el problema no es exactamente que el método de la clase padre no haga lo que nosotros queremos, sino que no hace todo lo que nosotros queremos. En ese caso es posible invocar desde el método de la clase hija al método de la clase padre empleando la palabra reservada  super .  super  es una referencia de sólo lectura que la proporciona de un modo automático el compilador (no necesitamos declararla ningún sitio) y que en una clase siempre apunta a su padre.

 

En el listado 7 podemos ver una segunda versión de la clase  Persona  (en este caso se llama  Persona2 ) que hereda de la clase  SerVivo . Para construir un objeto de la clase hija siempre será necesario invocar también a un constructor de la clase padre. Si en los constructores de la clase hija no se indica a qué constructor de la clase padre se debe llamar se llamará al constructor por defecto. Para indicar a qué constructor se debe invocar se emplea la sintaxis  super(argumentos) , en donde argumentos son los argumentos del constructor al que queremos invocar. En nuestro caso, estamos invocando al constructor por defecto. Si no hubiésemos colocado la sentencia  super()  al principio de cada constructor el compilador lo hubiese hecho por nosotros.

 

 Persona2  sobreescribe el método morir de  SerVivo . En esta ocasión no queremos hacer algo completamente diferente a lo que hace el método del padre, sino que queremos hacer algo más. Queremos imprimir por consola el nombre de la persona que se está muriendo. Por tanto, la primera sentencia del método morir en la clase hija lo que hace es invocar al método del padre. Después añade el código nuevo.

 

 
//LISTADO 7: Clase Persona2. Los métodos que no se muestran no han cambiado respecto a la clase Persona. 
//código Persona2.java del CD
public class Persona2 extends SerVivo{
    private int edad;
    private String nombre;
 
    public Persona2(){
         super();
         edad = 30;
         nombre = "Paco";
 
    }
 
    public Persona2(int _edad, String _nombre){      
         super();
         edad = _edad;
         nombre = _nombre;
    } 
    public void morir(){
        super.morir();
        System.out.println(nombre + " se ha muerto");
    }
...
}

 

 

El tutorial flash del CD muestra cómo emplear BlueJ para comprender mejor los efectos de la herencia.

 

3 Conclusiones

En este tercer artículo de la serie hemos comenzado a presentar el soporte que Java proporciona para la programación orientada a objetos, un paradigma de programación bastante diferente de la programación estructurada. Pero sólo hemos comenzado. En el siguiente artículo de la serie profundizaremos más en varios aspectos relativos a la herencia, presentaremos las interfaces y, finalmente presentaremos los  package . Os espero a todos el mes que viene.

 

 

Descargas

 

 

 

Cápitulos anteriores del curso:

 


martes
nov172009

Nueva version del programa de Gestión/Contabilidad EuroGes

Ya esta disponible para su descarga la nueva versión de EuroGes (V9.1.21)
(programa de gestión,administrativo,tienda ONLINE mediante Oscommerce y contabilidad para PYMES) con nuevas funcionalidades, mejoras en el aspecto de manejo de usuario y muchas nuevas funciones.
 
 Encontramos como mejoras mas importantes:
 
  Mayor estabilidad
  Mas rapidez
  Nuevos informes
  Mejor conexión con la tienda ONLINE Oscommerce.
  Opciones para clientes fuera de España adicionales
  Informe diario de operaciones
  Interconexion con couriers, incluyendo numeros de tracking y peticiones automáticas de recogidas
  Conexión con balanzas de peso.
  Digitalización de firmas.

Modulos de trazabilidad, numeros de series, fechas de caducidad.

Modulo de Taller:
  Control de garantias y envios de paqueterias.

Modulo para restaurantes:

  Diseño totalmente gráfico de las salas de mesas y sillas de un restaurant
  Selección gráfica de las comandas hacia los clientes
  Status más visibles en las mesas, con cambio de colores en base al status
 

Puede descargarse desde http://www.gtsiberica.com
martes
nov172009

El proyecto MaiTai, ¿showcase de JavaFX?

Hacia el final de la semana pasada el gurú de swing Josh Marinacci hizo pública la primera beta del proyecto Matai, proyecto que describe como "gráficos interactivos intoxicaantes". Se trata de una aplicación de escritorio desarrollada en JavaFX que permite al usuario crear distintos efectos gráficos conectando un conjunto de componentes disponibles en una paleta; cada componente aplica distintas transformaciones sobre una fuente de datos.


Matai requiere Java 6 para ejecutarse. Ha sido testado en Windows y Mac Os, pero todavía no en Linux (aquí tenéis una oportunidad para ayudar a Josh Marinacci). Se puede instalar a través de Java Web Start desde este enlaceAquí tenéis varios videos con animaciones creadas con esta herramienta.


Si decidís probarla, lo más fácil para comenzar a jugar con ella es cargar alguna de las animaciones demo disponibles desde el último botón de la derecha de la barra de herramientas.


Esta es la mejor demo de JavaFX que yo he visto hasta el momento. ¿Conocéis otras aplicaciones construidas sobre JavaFX que pudiesen servir para demostrar la tecnología?

 

 

lunes
nov162009

Curso de programación Java II - Abraham Otero

Curso de programación Java II

 

 Artículo publicado originalmente en la revista Sólo Programadores

 

En el número anterior presentamos brevemente qué es la plataforma Java y en qué consisten cada una de las tres ediciones de la plataforma (Java ME,  Java SE  y Java EE). Vimos cuáles eran los componentes básicos del kit de desarrollo Java y aprendimos a manejarlos desde una consola. Finalmente, presentamos cuáles son los tipos de datos primitivos y los operadores aritméticos, relacionales y lógicos básicos del lenguaje. En esta segunda entrega de este curso de programación Java presentaremos brevemente la librería matemática, introduciremos nuevos tipos de datos no primitivos como cadenas de caracteres, arrays y enumeraciones, y presentaremos las estructuras de control de flujo (bucles y condicionales) del lenguaje. Para trabajar con los códigos de ejemplo de este artículo os recomiendo que sigáis usando el JDK  para familiarizaros con él. En el número siguiente comenzaremos a usar un entorno de desarrollo. 

 

La librería matemática: la clase Math

 La mayor parte de las funciones matemáticas básicas (raíces cuadradas, exponenciación, logaritmos, senos, cosenos, senos hiperbólicos, etc.) están disponibles en Java a través de la clase Math. Hasta el siguiente número de este curso de programación Java no presentaremos qué son las clases y cómo construirlas. No obstante, no es necesario tener conocimientos de programación orientada a objetos para emplear esta clase: todos sus métodos son estáticos, esto es, se comportan como funciones. Por tanto, podemos invocarlos directamente sin necesidad de crear ningún objeto de la clase. La sintaxis de estos métodos es:

 

Math.metodo(argumentos);


Donde metodo  es el nombre del método concreto que queremos invocar. Por ejemplo, para realizar un seno emplearíamos Math.sin (ángulo) , y para calcular x^y en emplearíamos Math.pow (x,y) . En estos momentos probablemente te estés realizando preguntas como ¿Tengo que saber de memoria todos los métodos de la clase Math ? ¿Y también tengo que saber el orden en el que se le pasan los argumentos? y... ¿a Math.sin (ángulo)  el ángulo se lo debo de pasar en grados o en radianes?. Tranquilo, no es necesario saberse de memoria los métodos, ni los argumentos. Tampoco tienes que saberte de memoria si los ángulos se pasan en grados o en radianes. Aunque con la práctica acabarás sabiendo la respuesta a muchas de estas preguntas, no creo que tenga ningún sentido que te estudies la clase Math de memoria. 

 

Java tiene unas librerías extensas (extensísimas). Ningún desarrollador Java, por más gurú que sea, se las sabe de memoria. Pero lo que sí sabemos hacer los gurús es buscar rápidamente aquellas cosas que no sabemos en el javadoc de las librerías. Las librerías Java están formadas principalmente por código fuente Java. El javadoc de estas librerías no es más que el resultado de aplicar la herramienta javadoc, que presentamos en el primer artículo de esta serie, a dicho código fuente. 

 

Todo programador Java que se precie debe tener siempre a mano el javadoc de la librería estándar. Este javadoc puede descargarse desde la misma página de descarga donde se obtiene el JDK de Sun. Si el lector se ha instalado Netbeans, el entorno de desarrollo tiene esta documentación integrada y puede accederse poniendo el cursor sobre la clase o método del cual se quiera consultar el javadoc y a continuación pulsando Alt + F1. Esta documentación también está disponible online en http://java.sun.com/javase/6/docs/api/, aunque personalmente prefiero tenerla descargada en mi equipo ya que así puedo navegar de un modo más rápido través de ella y no dependo de tener conectividad para poder consultarla. 

 

 

FIGURA 1: El javadoc de la librería estándar es una herramienta indispensable para cualquier programador Java.


Como podemos ver en la figura 1, en el recuadro situado en la esquina inferior izquierda de la página principal de la documentación hay un listado, ordenado alfabéticamente, con todas las clases que componen la documentación de la librería estándar de Java. Tras hacer clic en Math  vemos la documentación de esta clase. Una de las primeras cosas que nos encontraremos es un listado de los métodos de la clase, junto con una descripción corta de su funcionalidad. Cada método es un enlace que nos lleva a una descripción más detallada, situada más abajo en la misma página web.

 

Volviendo a nuestra pregunta inicial ¿a Math.sin (ángulo)  el ángulo se lo debo pasar en grados o en radianes?. No tengo intención de responderte. Antes de seguir leyendo este artículo tú mismo deberías responder a esa pregunta consultando la documentación de la clase Math. Es muy importante acostumbrarse a usar el javadoc de la librería estándar; es un recurso esencial para cualquier programador Java. En el código del listado 1 podemos ver un programa donde se emplean varios de los métodos de la clase Math . 

 

//LISTADO 1: Este código muestra el funcionamiento de la clase Math
//código Ejemplo6.java del CD
int i = 45, j=2;
System.out.println ("Cos i : " + Math.cos(i));//coseno
System.out.println ("Sen i : " + Math.sin(i));//seno
System.out.println ("j^i : " + Math.pow(j,i)); //exponenciacion
System.out.println ("sqrt(j) : " + Math.sqrt(j)); //raiz cuadrada
System.out.println (" Número aleatorio : " + Math.random());//generacion de un numero aleatorio
System.out.println ("Valor absoluto: " + Math.abs(-5));//valor absoluto


Cadenas de caracteres: la clase String

 

 Para representar cadenas de caracteres en Java se emplea una clase, String , que es la que proporciona soporte para las operaciones que se realizan más comúnmente con cadenas de caracteres. La definición de un String  es similar a la de un tipo primitivo. Las cadenas de caracteres que vayan incluidas en el código Java como literales deben ir rodeadas por comillas dobles:

 

String e ;   //declarado pero no inicializado
String e ="";   //cadena vacia
String e = "Hola";  //inicializacion y asignacion
 


La concatenación de dos cadenas de caracteres en Java es muy sencilla: se realiza con el operador + ,  es decir “sumando” las cadenas de caracteres. Lo ilustraremos con un ejemplo: 

 

String saludo = "hola";
String nombre = "Pepe";
String saludaPepe = "";
saludaPepe = saludo + nombre;// saludaPepe toma el valor "holaPepe"
 


Si una cadena la intentamos concatenar con otro tipo de variable automáticamente se convierte la otra variable a String  (en el caso de que la otra variable sea un objeto para realizar la conversión se invocará el método toString ()  de dicho objeto), de tal modo que en Java es perfectamente válido el siguiente código:  

 

String saludo = "hola";
int  n = 5;
saludo = saludo + “"" + n;// saludo toma el valor "hola 5"


 Algunos métodos útiles de la clase String 

 

 A diferencia de C, las operaciones comunes sobre cadenas de caracteres, como la comparación o la extracción de una subcadena, no se realizan a través de funciones (o métodos estáticos), sino que se realizan a través de métodos de la propia clase String . Por ejemplo, en la clase String  hay un método que permite la extracción de una subcadena de caracteres de otra. Su sintaxis es:  

 

cadena.substring( int posiciónInicial, int posiciónFinal);


donde posiciónInicial  y posiciónFinal  son, respectivamente, la posición del primer carácter que se desea extraer y del primer carácter que ya no se desea extraer. Veamos un ejemplo de uso:  

 

String saludo = "hola";
String subsaludo = saludo.substring(0,2);// subsaludo toma el valor "ho"
 


También puede extraerse un char  de una cadena; para ello se emplea el método charAt(posición) , siendo posición la posición del carácter que se desea extraer. Se empieza a contar en 0.Para comparar cadenas de caracteres no puede emplearse el operador == . Este operador realiza una comparación de identidad, es decir, nos permitiría comprobar si dos objetos String  son realmente un mismo objeto. Pero no nos permite comprobar si dos objetos String  diferentes contienen el mismo texto. El segundo caso es el que se corresponde con la semántica de "igualdad" que habitualmente empleamos los seres humanos: no nos importa si son el mismo objeto, sino si su contenido es el mismo. Para comparar dos cadenas de caracteres debe emplearse otro método de String: equals. Su sintaxis es:  

 

cadena1.equals(cadena2);


el método devuelve true  si las dos cadenas son iguales y false  si son distintas.¿Es posible pasar una cadena de caracteres a minúsculas o a mayúsculas ? ¿y reemplazar un carácter por otro? ¿O encontrar la primera ocurrencia de un carácter de una cadena?. La respuesta es sí, todas estas operaciones, y muchas otras, son posibles. ¿Cómo se realizan?. Empleando métodos de la clase String . ¿Qué métodos?. A esta pregunta, ya me niego a responder. Recomiendo al lector que antes de continuar leyendo el artículo consulte el javadoc de la clase String  y se familiarice con las operaciones que soporta. En el listado 2 vemos un código que demuestra varias operaciones que se realizan con Strings. Este código también permite mostrar como en Java se distingue entre mayúsculas y minúsculas, de tal modo que "Hola"  no es la misma cadena que caracteres que "hola". 

 

//LISTADO 2: Operaciones frecuentes con cadenas de caracteres
//código Ejemplo7.java del CD
  String saludo = "Hola";
  String saludo2 ="hola";
  int n = 5;
//Imprime por consola la subcadena formada por los caracteres 
//comprendidos entre el caractero 0 de saludo y hasta el  carácter 2
  System.out.println( saludo.substring(0,2));
//ejemplo de concatenacion
  System.out.println( saludo +" " + n);
//Imprime el resultado del test de igualdad entre saludo y saludo2.
  System.out.println( "saludo == saludo2 "+ saludo.equals(saludo2));
 

 

Enumeraciones 

 

Esta característica del lenguaje sólo está disponible en Java 5 y superior. Si estás utilizando Java 1.4.X o inferior no podrás emplearla. La versión actual de Java es la 6.  

 

Los tipos de datos enumerados son un tipo de dato definido por el programador (no como ocurre con los tipos de datos primitivos). Sirven para representar variables que toman valor en un conjunto finito y, normalmente, pequeño de datos. Algunos ejemplos podrían ser los palos de la baraja española, o los días de la semana. Podríamos representar un palo de la baraja mediante un número entero; por ejemplo, podríamos asignar el 1 a los oros, el 2 a los bastos, el 3 a las copas y el 4 a las espadas. Esta estrategia tiene dos problemas: si una variable tipo int  es usada para representar un palo de la baraja, nada me impide asignarle el valor "-143", aunque no tenga ningún sentido. El segundo problema es que dentro de un mes no me voy a acordar qué palo representaba el 2, o si comencé a enumerar en 0 o en 1.

 

Las enumeraciones proporcionan una solución elegante a este problema e incrementan la legibilidad del programa. Para definirlas el programador debe indicar un conjunto de valores finitos sobre los cuales las variables de tipo enumeración deberán tomar valor. La mejor forma de comprender qué son es viéndolo; para definir un tipo de dato enumerado se emplea la sintaxis:

 

modificadores enum NombreTipoEnumerado{ VALOR1,VALOR2,.. }

 

Los posibles valores de modificadores  serán vistos en el siguiente capítulo de esta serie. El caso más habitual, y el que emplearemos por la de ahora, es que modificadores  tome el valor public . El nombre puede ser cualquier nombre válido dentro de Java. Entre las llaves se ponen los posibles valores que podrán tomar las variables de tipo enumeración, valores que habitualmente se escriben en letras mayúsculas. Un ejemplo de enumeración podría ser: 

 

public enum Semana {LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO}

 

Las definiciones de los tipos enumerados deben realizarse fuera del método main  y, en general, fuera de cualquier método; es decir, deben realizarse directamente dentro del cuerpo de la clase. Para definir una variable de la anterior enumeración se emplearía la siguiente sintaxis:

 

Semana variable;

 


y para darle un valor a las variables de tipo enumeración éstas deben asignarse a uno de los valores creados en su definición. El nombre del valor debe ir precedido del nombre de la propia enumeración:

 

variable = Semana.DOMINGO;

 

A diferencia de las enumeraciones de C o C++, las enumeraciones de Java son "typesafe", esto es, es imposible asignarle una variable de una enumeración un valor distinto de aquellos que se declararon al definir la enumeración. En C las enumeraciones internamente se representan como números enteros, por lo que nada nos impide asignarle a una variable de una enumeración un valor entero fuera del rango de las constantes simbólicas que se emplearon en su definición. En Java cada uno de los valores de las enumeraciones se representa mediante una instancia de la clase de la enumeración y el compilador garantiza que cada vez que se inicialice una enumeración se inicializa obligatoriamente a uno de los valores que se declararon en su definición, ya que estos valores son todas las instancias que existen de dicha clase.

 

En el listado 3 podemos ver un ejemplo de uso de enumeraciones.

 

 

//LISTADO 3: Ejemplo de uso de enumeraciones. 
//codigo Ejemplo8.java del CD
public enum Semana {LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO};
    public static void main(String[] args) {
        Semana hoy = Semana.MARTES;
//observa como gracias a la enumeración el programa es mas facil de leer 
        if(hoy == Semana.DOMINGO  || hoy == Semana.SABADO){
            System.out.println("Hoy toca descansar");
        } else{
            System.out.println("Hoy toca trabajar");
        }        
    }

 

 

Arrays

 

Un array es una colección de variables del mismo tipo, que tienen un nombre común y están dispuestos en posiciones consecutivas de la memoria del ordenador. Cada elemento del array se distingue de los demás por su índice (número de orden dentro del array). Un array se caracteriza por su tipo base (el tipo de cada elemento), el número de dimensiones del mismo y la longitud en cada dimensión. 

 

En Java los arrays son un objeto. Como tales se crean mediante el operador de new  (en el siguiente capítulo veremos el uso de este operador de un modo detallado). Quitando esa diferencia, la forma de crear el array y la forma de trabajar con ellos es idéntica a la forma en la que se trabaja con los arrays en C. La sintaxis en la definición de un array de una dimensión es la siguiente:

 


Tipo_datos[] nombreArray = new Tipo_datos[tamañoArray];

 

Tipo_datos  es el tipo de los datos que se almacenarán en el array (int , char , String ... o cualquier objeto). TamañoArray  es el tamaño que le queremos dar a este array. Por ejemplo, la sentencia:

 

 
int[] array = new int[10];

 

crea un array llamado array , en el que podremos almacenar 10 datos tipo entero. Para crear una matriz, esto es, un array de dos dimensiones, emplearemos el siguiente código:

 

 
int[][] matriz = new int[3][9];

 

Como se muestra en la figura 2, el primer elemento de un array se sitúa en la posición 0, exactamente igual que en C. Una vez definido, para acceder a los elementos de un array usaremos el nombre del array y a continuación, entre corchetes, el índice del elemento al cual queremos acceder. Por ejemplo, la instrucción:

 

 
array[5] = array[0] + array[3];

 

hace que la sexta posición del array tome el valor de la suma de la primera posición del array más la cuarta (una vez más, hagamos énfasis en que se comienza a contar en cero). En el caso de las matrices, o poliedros en general, deberemos emplear un índice por cada dimensión del poliedro.

 

 

FIGURA 2: En Java el primer índice de todas las dimensiones de un array comienza a contar en 0.

 

El listado 4 muestra un código donde se inicializa un array con números aleatorios entre 0 y 80 y a continuación se calcula la suma de dichos números.

 

//LISTADO 4: Codigo de ejemplo que muestra el uso de arrays en Java.
//codigo Ejemplo9.java del CD
     int[] edades = new int[10];
    for(int i= 0; i< 10; i++){
   edades[i] = (int)Math.random()% 80;
               System.out.println("Elemento" + i + edades[i]);
 	  }
int sum = 0;
    for(int i= 0; i< 10; i++){
   sum  = sum + edades[i];
  }
System.out.println("Suma " + sum);

 

 

El control de flujo en Java

 

El modo de  ejecución de un programa Java en ausencia de elementos de control de flujo es secuencial, es decir, las instrucciones se van ejecutando una detrás de otra, se ejecutan todas, y sólo se ejecutan una vez. Esto nos permite hacer programas muy limitados; para evitarlo Java emplea  la misma solución que emplean la inmensa mayoría de los lenguajes de programación: estructuras de control de flujo. Estas estructuras de control de flujo se dividen en dos grupos: los condicionales, que permiten ejecutar o no un bloque de código dependiendo de que se cumpla una determinada condición; y los bucles, que permiten repetir la ejecución de un bloque de código mientras se cumpla una determinada condición.

 

Aquellos lectores familiarizados con C comprobarán cómo el 90% de la sintaxis de Java referente a estructuras de flujo es idéntica a la de este lenguaje.

 

 

Estructuras de control de flujo condicionales

 

Las estructuras de control de flujo de tipo condicional ejecutan un código u otro en función de que se cumpla o no una determinada condición. En Java existen dos tipos de condicional: if  y switch .

 

Condicional tipo if

 La sintaxis más sencilla para el condicional if  es:

 
if(condicion) {
Grupo de sentencias;}

 

condicion  tiene que ser un valor tipo boolean ; este valor puede obtenerse evaluando una expresión indicada por condición, pero el valor de evaluar dicha expresión tiene que ser obligatoriamente un boolean  (a diferencia de C, no se pueden emplear números enteros como condiciones). El grupo de sentencias que se encuentra entre llaves se ejecuta sólo si la condición toma un valor true . En caso contrario se ignora el grupo de sentencias. Si el condicional afecta a una única sentencia las llaves son opcionales.

 

Un if  puede ir acompañado de un bloque else :

 
if(condicion) {
Grupo de sentencias;}
else{
Grupo2 de sentencias;}

 

 

Si condicion  toma el valor true  se ejecuta Grupo de sentencias , en caso contrario se ejecuta Grupo2 de sentencias . Entre el if  y el else  no puede ir ninguna sentencia.

 

Es posible anidar varios condicionales tipo if :

 
if(condicion) {
Grupo de sentencias;}
else if (condicion2){
Grupo2 de sentencias;}
else if (condicion3){
Grupo3 de sentencias;}
...
else{
Grupo_n de sentencias;}

 

Si condicion  toma el valor true  se ejecuta Grupo de sentencias  y se ignoran todos los demás condicionales y el else . En caso contrario, si condicion2  toma el valor true  se ejecuta Grupo2 de sentencias ... y así sucesivamente hasta acabarse todas las condiciones. Si no se cumple ninguna se ejecuta el grupo de sentencias asociadas con el else . Este último else  es opcional. 

 

En el listado 5 mostramos un ejemplo de uso de condicional tipo if . Este es el primer ejemplo donde usamos otro método además del main , el método static int test(int val, int val2) . Un método estático es equivalente a una función; es decir, puede invocarse directamente sin necesidad de crear ninguna instancia de la clase. El nombre del método es test  y recibe dos parámetros (val  y val2 ) de tipo entero. El método devolverá como resultado de su ejecución un valor entero; dicho valor debe indicarse empleando la sentencia al  return . En el siguiente artículo de esta serie presentaremos en detalle la sintaxis para declarar métodos en Java.

 

El método test  es invocado varias veces desde el main . Este método devuelve -1 si el primer argumento es menor que el segundo, +1 si el primer argumento es mayor que el segundo y 0 si ambos son iguales.

 

//LISTADO 5: Ejemplo de uso del condicional tipo if.
//codigo Ejemplo8.java del CD
  static int test(int val, int val2) {
    int result = 0;
    if(val > val2)
      result = +1;
    else if(val < val2)
      result = -1;
    else
      result = 0; 
    return result;
  }
  public static void main(String[] args) {
    System.out.println(test(10, 5));
    System.out.println(test(5, 10));
    System.out.println(test(5, 5));
  } 

 

 

Switch 

Esta estructura de control de flujo permite elegir entre varios bloques de código cuáles se deben ejecutar y cuáles no. Veamos su sintaxis:

 
switch(selector) {
  	case valor1 : Grupo de sentencias1; break; 
 	case valor2 : Grupo de sentencias2; break;
 	case valor3 : Grupo de sentencias3; break;
  	case valor4 : Grupo de sentencias4; break;
 	case valor5 : Grupo de sentencias5; break;
          	// ...
  	default: statement;
}

 

al ejecutarse la sentencia switch  compara el valor de selector  con valorX . Si el valor coincide se ejecuta su respectivo grupo de sentencias. Si no se encuentra ninguna coincidencia se ejecutan las sentencias del default . Si no se pusieran los break   una vez hubiese una coincidencia entre un valor y el selector se ejecutarían todos los grupos de sentencias, incluida la del default . En la gran mayoría de los casos, éste no es el comportamiento que se desea de la sentencia switch , de ahí que cada uno de los case habitualmente termine con un break , es decir, con un salto incondicional fuera de la estructura de control de flujo.

 

La variable selector sólo puede ser de tipo char  o cualquier tipo de valor entero menos long  (si estás empleando Java 5 o posterior también se pueden emplear enumeraciones). No podemos emplear ningún tipo de número real, Strings, u objetos en general.

 

En el listado 6 mostramos un ejemplo de uso de la sentencia switch . En este caso, tomamos ventaja de que una vez que hay una coincidencia entre la variable que hace las veces de selector y uno de los valores de los case  se ejecutan todos los bloques de código de todos los case que haya a continuación y el bloque de código del default . El programa genera una letra aleatoria del alfabeto y emplea un switch  para comprobar si la letra es una vocal o una consonante; si es una vocal imprime "vocal" por la consola, y si es una consonante imprime "consonante". El principio códigos o promotores switch  se emplea para comprobar si el carácter es una de las cinco vocales del alfabeto, por tanto hay que hacer cinco comparaciones diferentes. Pero la acción a realizar en los cinco casos es la misma: imprimir "vocal". De ahí que podamos tomar ventaja del particular comportamiento del switch .

 

//LISTADO 6: Ejemplo de la estructura de control de flujo switch.
//codigo Ejemplo11.java del CD
//Bucle for. Ejecutará 100 veces el codigo que tiene dentro.
    for(int i = 0; i < 100; i++) {
//generamos un carácter aleatorio
      char c = (char)(Math.random() * 26 + 'a');
      System.out.print(c + ": ");
      switch(c) {
      case 'a':
      case 'e':
      case 'i':
      case 'o':
      case 'u':
//Si el caracter es ‘a’, ‘e’, ‘i’, ‘o’ o ‘u’ imprimimos vocal. 
                System.out.println("vocal");
                break;
      default:
//Si no era ninguna de las anterioes imprimos consonate.
                System.out.println("consonante");
      }
    }
  } 

 

 

Bucles 

 

Son instrucciones que nos permiten repetir un bloque de código mientras se cumpla una determinada condición. En Java existen cuatro tipos diferentes: while , do while , for , y (en Java 5) "for each".

 

Bucle while

Cuando en la ejecución de un código se llega a un bucle while  se comprueba si se verifica la condición asociada con él, si se verifica se ejecuta el bloque de código asociado con el bucle y se vuelve a comprobar la condición; mientras dicha condición siga verificándose se seguirá ejecutando el código del bucle. Su sintaxis es:

 
while(condición){
Grupo de sentencias;}

 

 

El listado 7 muestra el funcionamiento de este bucle. En él se emplea el método random   de la librería matemática de Java para generar un número aleatorio. Mientras el número aleatorio que se genera sea menor que 0.99 (ver condición del bucle) se vuelve a generar otro número aleatorio y se imprime dicho número por consola.

 

//LISTADO 7: Ejemplo de bucle tipo while.
//codigo Ejemplo12.java del CD
    double r = 0;
//Mientras que r < 0.99 sigue ejecutando el cuerpo del bucle.
    while(r < 0.99) {
//Genera un nuevo r aleatario entr 0 y 1.
      r = Math.random();
      System.out.println(r);
    }
 

 

El bucle do while

Su comportamiento es semejante al bucle while , sólo que aquí la condición va al final del código del bucle, por lo que el código se va a ejecutar al menos una vez. Como norma general, debemos emplear un bucle tipo while  cuando haya un conjunto de instrucciones que se puedan repetir de 0 a n veces, y un bucle do while  cuando tengamos un conjunto de instrucciones que se puedan repetir de 1 a n veces. La sintaxis de do while  es:

  
do {
Grupo de sentencias;
}while(condición);

 

 

En el listado 8 podemos ver el código de listado 7 implementado mediante un bucle do while.

 
//LISTADO 8: Ejemplo de bucle tipo do while.
//código Ejemplo13.java del CD
    double r;
    do {
      r = Math.random();
      System.out.println(r);
    } while(r < 0.99d);
  }
 

 

Bucle for 

La sintaxis de este bucle es la siguiente siguiente:

 

for(expresion1;expresion2;expresion3){
Grupo de sentecias;}
 

 

Expresion1  es una expresión cualquiera, en la cual es posible declarar y/o asignar valor a una variable, la variable-condición del bucle. Expresion2  es la condición del bucle. Mientras dicha condición tome el valor true  el bucle se repetirá una y otra vez. Habitualmente, esta expresión comprueba alguna condición sobre una variable declarada o asignada en la primera expresión. Expresion3 indica una operación que se realiza en cada iteración a partir de la primera (en la primera iteración el valor de la variable del bucle es el que se le asigna en expresion1 ); nuevamente, lo más habitual es que esta operación se realice sobre la variable del bucle. El bucle for  anterior es completamente equivalente al siguiente código:

 
expresion1; 
while (expresion2) 
  { 
  Grupo de sentecias;
 expresion3; 
 }
 

 

En el listado 9 se emplea un bucle for  para imprimir por consola los caracteres de la tabla ASCII. Cuando el carácter se imprime haciéndole un cast a int  se mostrará el valor entero correspondiente, y cuando se imprime como carácter se mostrará el carácter asociado a dicho valor entero en la tabla ASCII.

 
//LISTADO 9: Este pequeño código imprime por consola la tabla ASCII empleando un bucle tipo for.
//código Ejemplo14.java del CD
  for( char c = 0; c < 128; c++)
      System.out.println("valor: " + (int)c + " caracter: " + c);
  } 

 

 

Bucle for-each

Esta característica sólo está disponible en Java 5 y versiones posteriores. Se trata de un bucle diseñado con el propósito de recorrer un conjunto de objetos. Por lo de ahora la única estructura que hemos visto que permite almacenar un conjunto de objetos son los arrays, pero en números posteriores de esta serie de artículos introduciremos el framework de colecciones de datos de Java. Esta estructura resulta particularmente útil para trabajar con dichas colecciones (listas, pilas, mapas...). La sintaxis de este bucle es:

 

 
for(Tipo elemento: colecciónElementos){
Grupo de sentecias;}

 

Tipo  es el tipo de dato de los elementos del conjunto; elemento  es una variable auxiliar que la primera vez que se ejecute el bucle tomará el valor del primer elemento del conjunto, la segunda vez tomará el valor del segundo elemento del conjunto y así sucesivamente. La colecciónElementos  es el conjunto de elementos sobre los cuales queremos iterar (por lo de ahora, un array). El bucle se repetirá una vez para cada elemento de la colección. 

 

Este bucle "foreach” se llama "for", aunque hubiese sido mejor emplear el primer nombre por similitud con otros lenguajes de programación que poseen un bucle similar, por haber sido introducido en el lenguaje de programación a posteriori. foreach no es una palabra reservada dentro de Java; si cuando se diseñó Java 5 se hubiese empleado esta palabra para este tipo de bucle podrían producirse incompatibilidades con código fuente Java escrito antes de ese momento.

 

En el listado 10 podemos ver cómo se emplea este tipo de bucle para calcular la suma de los elementos de un array.

 

//LISTADO 10: Ejemplo de bucle "foreach" en Java.
//código Ejemplo15.java del CD
        int array[] = new int[10];
        int suma = 0, contador = 0;
        //con este bucle damos valores a los elementos del array
        for (int i = 0; i < array.length; i++) {
            array[i]= 2*i;
        }
        for (int e : array){  //para cada elemento del array
            suma = suma + e;
        }
        System.out.println(suma);
    }
 

 

break y continue

Estas dos instrucciones no son ni un bucle ni un condicional, pero sí son sentencias íntimamente relacionada con estas estructuras de control de flujo. El encontrarse una sentencia de break  en el cuerpo de cualquier bucle detiene la ejecución de su cuerpo y produce un salto incondicional a la sentencia que se encuentra inmediatamente después del bucle. Como ya hemos mostrado en este artículo, esta sentencia también se puede usar para forzar la salida del bloque de código asociado con uno de los case  de una instrucción tipo switch.

 

continue  también detiene la ejecución del cuerpo del bucle, pero en esta ocasión no se sale del bucle, sino que se ignora el resto del cuerpo del bucle que quedase por ejecutar y se vuelve al principio. 

 

En el listado 11 podemos ver un ejemplo de uso de estas sentencias. En el primer bucle for , continue  se emplea para ignorar todos los números enteros que no sean múltiplos de 9. En el segundo bucle, break  se emplea para salir de un bucle infinito.

 

//LISTADO 11: Ejemplo de uso de las sentencias break y continue.
//codigo Ejemplo16.java del CD
    for(int i = 0; i < 100; i++) {
//Salto a la siguiente iteracion si i no es divisible entre 9
      if(i % 9 != 0) continue;
//Si i es divisible entre 9 se imprime
      System.out.println(i);
    }
    int i = 0;
    // Lazo infinito del cual se sale con break:
    while(true) {
      i++;
      if(i == 120) break; // Salimos del lazo 
      System.out.println(i);
    } 
 

 

Conclusiones

 

En este segundo artículo de la serie hemos presentado la librería matemática de Java, varios tipos de datos no primitivos como arrays, cadenas de caracteres y enumeraciones, y los elementos básicos de control de flujo (bucles y condicionales) del lenguaje.

 

En el siguiente artículo de la serie comenzaremos a introducir la programación orientada a objetos en Java. Explicaremos qué es una clase, cómo trabajar con ellas, y en qué consiste la herencia. También comenzaremos emplear un entorno de desarrollo, BlueJ, para trabajar con los códigos de ejemplo. Os espero a todos el mes que viene.

 

 

Descargas

 

 

 

 

Cápitulos anteriores del curso:

 

Curso de programación Java I - Abraham Otero 

lunes
nov162009

primer dia en el devoxx

Ya ha empezado el devoxx, asique como lo prometido es deuda aqui estamos retransmitiendo.

La primera conferencia ha sido sobre Java Generics. Ha explicado desde cero como funcionaban lod Generics y alguna cosa un poco más avanzada de como usar wildcards, problemas de los mismos y como se deben usar correctamente los Generics y como restreingir los tipo. Finalmente, ha explicado como se puede crear un framework usando de manera eficiente los templates. También ha ido haciendo en paralelo una transformación de un ejemplo de framework  normal a parametrizarlo usando generics.

 URL:  www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html

 

la segunda conferencia ha sido sobre JSF 2.0, pero en general ha sido cosas que han  cambiado respecto a la primera versión. Han explicado de manera muy breve muchas caracteristicas de JSF como la creacion de componentes, la validación de Beans (que se incorporará como caracteristica a Java EE 6 ), la conservación de estados dentro de una sesión (que puede ser parcial o completa).

 Tambien se ha incorporado un gestor de peticiones que controla la información recibida en la petición, pudiendose pasar los mismos como parte de la URL de manera que sea fácil de recordar o repeti.

URLs:

  • http://archives.java.sun.com/jsr-314-open.htm
  • http://javaserverfaces-spec-public.dev.java.net
  • http://jcp.org

Despues ha habido 2 sesiones de conferencias cortas. La primera ha tratado sobre JPA (Java Persistence API) en la que ha contado basicamente el producto que habia desarrollado su empresa: Hades. El framework presentado tenia cosas interesantes como la posibilidad de personalizar las queries mediante anotaciones dentro del modelo que representaba la tabla. Estos parametros se pueden cablear dentro de la anotacioón o lo que es mucho más práctico, recibir los parametros de busqueda inyectandolos directamente desde un método. Este desarrollo es Open Source y se encuentra bajo la licencia Apache.

URL: http://hades.synyx.org

 La última sesión del día ha sido sobre NoSql. La charla deberia haber sido sobre Cassandra, una implementacion de este sistema de DB, pero realmenteha tratado sobre HBase y un sistema CMS desarrollado por la empresa del conferenciante (Daisy). Básicamente ha contado las dferencias con los tradicionales sistemas relacionales, consigue mejorar la velocidad de las operaciones gracias a perder completamente la consistencia de datos. 

URLs:

  • cs.commell.edu/projects/ladis2009/talks/ramakrishnan-keynote-ladis2009.pdf
  • hadoop.apache.org/hbase

 

A su vez hemos asistido también a otra conferencia sobre Spock. Debido a que el conferenciante se encontraba enfermo, ha asistido otra persona y ha hablado sobre JRockit. Más concretamente sobre JRockit Mission Control. La charla se ha centrado en los tipos de mecanismos de test mediante lo que ellos llaman black box. Y de como mediante técnicas como GC controlan el acceso de objetos a memoria, como controlar la  latencia, accesos a métodos etc. Estas tecnicas se utilizan mediante unas herramientas que incorpora jrockit llamadas mission control.

Ha hablado mucho sobre su producto sin centrarse en como funciona o trabaja, lo cual ha sido una pena porque las transparencias que ponía de ejemplo del programa parecían muy interesantes. 

  • http://oracle.com/technology/products/jrockit/missioncontrol/index.com
  • htttp://oracle.com/jrockit