Introducción a la Programación

2 Empezando por el Principio

En este capítulo vamos a introducir el primer programa Java y el modo de construir un programa y ejecutarlo. También daremos un repaso al modo de capturar datos de entrada en un programa; y de mostrar los resultados del mismo por la pantalla.

Además, introduciremos los primeros conceptos relacionados con la programación Orientada a Objeto.

2.1 El Primer Programa

Los programas Java se crean con clases. A partir de una definición de clase podemos crear cualquier cantidad de objetos, que se conocen como instancias u ocurrencias de esa clase. Se puede decir que una clase es algo parecido a los planos para hacer un objeto, una mesa por ejemplo, y un objeto de la clase mesa es una mesa realizada con esos planos. Puede suceder que con la misma descripción de una mesa como un objeto con una plataforma rectantular soportada por cuatro patas se obtengan mesas de diferente tamaño, color, altura, material, pero todas serán mesas y compartirán unas características comunes.

Una clase contiene dos tipos de miembros, llamados campos y métodos. Los campos son los datos que va a almacenar la clase o los objetos de esa clase, y los métodos son los conjuntos de sentencias que van a manipular los campos de esa clase.

El primer ejemplo de programa que se hace siempre que se comienza a aprender un lenguaje de programación es el que muestra en pantalla un mensaje de saludo. Este primer programa en Java puede ser el siguiente:

class HolaMundo{
   public static void main(String[] args) {
      System.out.println("Hola mundo");
   }
}

Para conseguir que ese código Java se convierta en un programa que al ejecutarlo nos salude hay que seguir varios pasos:

  1. Este primer programa se va a escribir utilizando un editor de texto. Así, obtendremos un fichero con el código fuente que llamaremos HolaMundo.java .
  2. El siguiente paso consiste en compilar el fichero con el cógido fuente para obtener los bytecodes correspondientes. Esto se puede hacer ejecutando el siguiente comando:
  3. javac HolaMundo.java
    

    Como resultado de la compilación vamos a obtener una serie de ficheros de bytecodes, uno por clase descrita en el fichero fuente. En nuestro caso obtendremos un único fichero con bytecodes llamado HolaMundo.class.

  4. Ahora estamos en disposición de ejecutar ese fichero bytecode sobre una máquina virtual Java. Habitualmente existirá un intérprete Java que simulará que disponemos de una máquina Java. Esto se consigue con el comando
  5.  java HolaMundo 

    que dará como resultado el saludo esperado.

    Hola mundo

Ya tenemos un pequeño programa Java que hace algo pero, ¿qué significa? El programa anterior declara una clase llamada HolaMundo con un único método llamado main. Los miembros de la clase aparecen entre llaves, {, y , }, detrás del nombre de la clase. La clase HolaMundo tiene un solo método y ningún campo (no necesita almacenar información). Esto puede cambiarse, si consideramos el mensaje de saludo como un dato a guardar en una variable. Entonces tendríamos la clase HolaMundo2, tal y como se puede ver a continuación:

class HolaMundo2{
   public static void main(String[] args) {
      String mensaje="Hola otra vez mundo";   
      System.out.println(mensaje);
   }
}

El único parámetro del método main es un array de objetos String, o cadenas de caracteres, que contiene los argumentos con que se invocó al programa. Aunque de todo eso nos ocuparemos más adelante.

El método main se declara void porque no devuelve ningún valor. Es uno de los pocos nombres especiales de método en Java, y si se declara como hemos visto anteriormente, se ejecutará cuando ejecutemos la clase como una aplicación. Cuando se ejecuta main puede crear objetos, evaluar expresiones, invocar otros métodos y hacer cualquier otra cosa necesaria para conseguir que la aplicación se comporte de la manera deseada.

En el ejemplo anterior, main contiene una sentencia única que invoca un método en el objeto out de la clase System. Los métodos se invocan suministrando una referencia de objeto y un nombre de método, separados por un punto,'.'. Así, HolaMundo usa el método println del objeto out para imprimir una cadena con un salto de línea al final en la salida estándard (el monitor).

2.2 Clases y Objetos

La unidad fundamental de programación en Java es la clase. Las clases contienen campos, almacenes de información, y métodos, colecciones de código ejecutable que modifican los campos.

Cada objeto es una instancia de una clase. Esto es, es la implementación de las características descritas por la clase a la que pertenece. Cuando se invoca un método sobre un objeto, se examina la clase para encontrar el código que se va a ejecutar.

2.2.1 Una Clase Simple

Los elementos fundamentales de una clase son sus campos (datos) y sus métodos (código para manipular los datos). Para entender todo esto vamos a construir una clase sencilla que sirva para almacenar información sobre los cuerpos celestes.

class Cuerpo{
   public static long sigID=0;
   public long idNum;
   public String nombre;
   public Cuerpo gira;
}

En primer lugar declaramos el nombre de la clase. Una declaración de clase crea un nombre de tipo Java, de forma que las referencia mercurio a un objeto de ese tipo se puede declarar como Cuerpo mercurio;. A partir de este momento, mercurio es una referencia a un objeto de la clase Cuerpo. La declaración no crea el objeto, únicamente declara una referencia a un objeto Cuerpo. La referencia es inicialmente null y el objeto referenciado por mercurio no existe en realidad hasta que se crea explícitamente.

Esta primera versión de Cuerpo está mal diseñada de modo intencionado, e iremos perfeccionándola a medida que avancemos en el conocimiento del lenguaje.

2.2.2 Campos

Las variables de una clase de denominan campos. La clase Cuerpo tiene cuatro campos: sigID para almacenar el siguiente identificador a asignar, idNum el identificador de este cuerpo celeste, nombre su nombre y gira el cuerpo celeste alrededor del que gira.

Cada objeto tiene una instancia diferente de los campos de la clase. Esto quiere decir que cada objeto tiene un nombre y ese nombre no afecta a los nombres de los demas objetos, ni es afectado por ellos. A veces, sin embargo, se desea que un campo sea compartido por todos los objetos de una clase. Esto se consigue declarando los campos static, por lo que se denominan campos estáticos o de clase. En el ejemplo, el campo sigID es un campo estático y único para todos los objetos Cuerpo y es compartido por todos los objetos que se creen.

2.2.3 Creación de Objetos

Vamos a crear dos objetos Cuerpo, para almacenar información sobre el sol y sobre la tierra.

Cuerpo sol = new Cuerpo();
sol.idNum = Cuerpo.sigID++;
sol.nombre = "Sol";
sol.gira = null;
Cuerpo tierra = new Cuerpo();
sol.idNum = Cuerpo.sigID++;
tierra.nombre = "Tierra";
tierra.gira = sol;

Primero decaramos dos referencia (sol y tierra) pra albergar los objetos de tipo Cuerpo. Estas declaraciones no crean objetos, sólo declaran variables que hacen referencia a objetos. Las referencias son inicialmente null, y los objetos que pueden referenciar deben crearse explícitamente.

Creamos el objeto al que apuntará sol utilizando el operador new especificando el tipo de objeto que se desea crear y los posibles parámetros para su construcción.

Una vez creado el nuevo objeto, inicializamos sus campos. Cada objeto Cuerpo necesita un identificador único, que se obtiene del campo estático sigID de Cuerpo. El código debe incrementar sigID de modo que el siguiente objeto Cuerpo que se cree obtenga otro identificador exclusivo.

2.2.4 Constructores

A un objeto recien creado se le da un estado inicial. Los campos se pueden inicializar con un valor cuando se declaran, lo que a veces es suficiente. Pero con frecuencia se necesita más que una simple inicializacion con datos para crear el estado inicial, y tal vez sea necesario que el código de creación proporcione datos iniciales o realice operaciones que no se pueden expresar como una simple asignación.

Para fines distintos de la inicialización simple, las clases pueden tener constructores. Los constructores tienen el mismo nombre que la clase que inicializan y pueden tener o no parámetros entre paréntesis. Los constructores se invocan después de haber asignado los valores iniciales por defecto a los campos de un objeto recién creado, y después de ejecutar sus inicializaciones explícitas.

Esto se puede ver en la siguiente versión de la clase Cuerpo:

class Cuerpo{
   private static long sigID=0;
   public long idNum;
   public String nombre="";
   public Cuerpo gira=null;
 
   Cuerpo(){
      idNum=sigID++;
   }

El constructor para Cuerpo no toma argumentos, pero realiza una función importante: asigna un idNum adecuado al objeto recién creado. Como el constructor Cuerpo es ahora el único que tiene que tener acceso al campo sigID, debemos hacer que sigID sea privado, de forma que sólo la clase Cuerpo tenga acceso a él.

El código de creación del sol y de la tierra quedará como sigue.

Cuerpo sol = new Cuerpo();
sol.nombre = "Sol";
Cuerpo tierra = new Cuerpo();
sol.idNum = Cuerpo.sigID++;
tierra.nombre = "Tierra";
tierra.gira = sol;

Como las referencias a objetos se inicializan a null por defecto, el campo sol.gira no se inicializa explícitamente.

Dado que parece bastante razonable que cada vez que se cree un objeto Cuerpo se le asigne un nombre y el cuerpo sobre el que orbita, deberíamos dotar de otro constructor a la clase Cuerpo, esta vez con dos argumentos:

   Cuerpo(String nombreCuerpo, Cuerpo giraAlrededor){
      idNum=sigID++;
      nombre=nombreCuerpo;
      gira=giraAlrededor;
   }

La creación de los objetos sol y tierra puede quedar ahora de la siguiente manera:

Cuerpo sol = new Cuerpo("Sol",null);
Cuerpo tierra = new Cuerpo("Tierra",sol);

Si no se proporcionan constructores de ningún tipo en una clase, el lenguaje proporciona un constructor sin argumentos por defecto.

2.2.5 Métodos

Los métodos de una clase contienen habitualmente código que comprende y manipula el estado de un objeto, sus campos.

Los métodos se invocan como operaciones sobre los objetos a través de las referencias usando el operador . como sigue:

referencia.método(parámetros)

Cada método puede tener un número de parámetros distinto, pero no un número variable de ellos. Cada parámetro tiene un tipo de dato, y los métodos devuelven un valor de un tipo dado. Un ejemplo puede ser el método toString que convierte la información de un objeto Cuerpo en una cadena de caracteres lista para ser impresa.

   public String toString(){
      String desc=idNum + " ("+ nombre + ")";
      if (gira != null)
         desc += " gira " + gira.toString();
      return desc;
   }

El método toString es un método especial, ya que es el invocado para obtener una cadena de caracteres, un String, cuando un objeto se utiliza en una concatenación de cadenas de caracteres con el operador +.

Así, las siguientes invocaciones

      System.out.println("cuerpo " + sol);
      System.out.println("cuerpo " + tierra);

producen los siguientes resultados, respectivamente:

cuerpo 0 (Sol)
cuerpo 1 (Tierra) gira 0 (Sol)

Si un método no devuelve ningún valor, se dice que devuelve el tipo void.

Los detalles de invocación de una aplicación Java varían de un sistema a otro pero, independientemente de estos, siempre hay que proporcionar el nombre de una clase Java que es la que controla la ejecución del programa. Cuando se ejecuta un programa Java, el sistema localiza y ejecuta el método main de la clase invocada. El método main debe ser public static void y debe aceptar un sólo argumento String [].

Uso de los métodos para controlar el acceso

Algunas clases tienen campos public para que los programadores los manipulen directamente, pero en la mayoría de los casos esta idea no es buena, y es mejor hacer los campos private y que su manipulación sea siempre a través de métodos públicos, public. De este modo controlamos el contenido de los campos.

Esto es lo que hemos de hacer con el campo idNum, que aunque ahora se asigna correctamente, puede ser manipulado por un programador, haciendo que su valor cambie y dejando sin efecto el control de los identificadores que tenemos. Así, lo mejor será que el campo idNum sea de sólo lectura, private, y accesible sólo a través de métodos, que modifiquen o obtengan su contenido. Esto es lo que hacemos en la siguiente, y última, versión de la clase Cuerpo:

class Cuerpo{
   /* campos */
   private static long sigID=0;
   private long idNum;
   public String nombre="";
   public Cuerpo gira=null;
 
   /* constructores */
   Cuerpo(){
      idNum=sigID++;
   }
   Cuerpo(String nombreCuerpo, Cuerpo giraAlrededor){
      idNum=sigID++;
      nombre=nombreCuerpo;
      gira=giraAlrededor;
   }
 
   /* metodos */
   public String toString(){
      String desc=idNum + " ("+ nombre + ")";
      if (gira != null)
         desc += " gira " + gira.toString();
      return desc;
   }
 
   public long id(){
      return idNum;
   }
 
   public static void main(String [] args){
      Cuerpo sol = new Cuerpo("Sol",null);
      Cuerpo tierra = new Cuerpo("Tierra",sol);
 
      System.out.println("cuerpo " + sol);
      System.out.println("cuerpo " + tierra);
   }
}

Ahora los programadores que deseen usar el identifiacdor del cuerpo invocarán el método id, que devuelve el valor del identificador.

Aún en el caso de que una aplicación no requiera campos que sean de sólo lectura, hacer privados los campos y agregarles métodos para asignarles valores y consultarlos permite añadir acciones que tal vez sean necesarias en el futuro.

2.3 Bibliografía


Jesús Vegas
Dpto. Informática
Universidad de Valladolid
jvegas@infor.uva.es