sábado, 30 de noviembre de 2013

Introducción a Java

Creando nuestra primera clase

Vamos a empezar pronto con las cosas prácticas creando nuestra primera clase y viendo así cómo se programa, compila y ejecuta el código Java. En Java, todo lo que hagamos será programar clases. Cualquier programa será una clase que a su vez podrá hacer uso de una, dos, tres o más clases. Así pues, escribiremos el siguiente código:
HolaMundo.java
public class HolaMundo {
  public static void main(String[] args) {
    System.out.println("Hola, mundo");
  }
}
Este ejemplo expone varios elementos del lenguaje Java a los que tendremos que prestar atención. Pero lo primero es editar este fichero y grabarlo con el nombre de MiPrimerPrograma.java. Si tenemos bien instalado el JDK, no tenemos más que compilarlo escribiendo:


javac HolaMundo.java
Y ejecutarlo:
java HolaMundo
Una cosa a recordar es que ejecutamos "HolaMundo", no "HolaMundo.class". El intérprete añade la extensión.
Veremos que nos muestra el mensaje "Hola, mundo" en la pantalla al ejecutarlo, tal y como cabe esperar de un primer programa en cualquier lenguaje. Vamos a analizarlo línea a línea:
public class HolaMundo {
}
Con esto estamos indicando que todo lo encerrado entre llaves pertenece al código de la clase HolaMundo. En Java, el ámbito de algo está indicado por las llaves. ¿Y qué es el ámbito? El ámbito es lo que determina tanto la visibilidad de la definición de un nombre. Por lo tanto, todo nombre definido entre las llaves arriba indicadas (ya corresponda a una propiedad, variable o método) será visible dentro de esas mismas llaves y dentro de sus llaves "hijas". Por ejemplo:
{
  int x1;
  {
    int x2;
  }
  {
    int x3;
  }
}
En este ejemplo, x1 sería visible en todos los ámbitos, mientras que x2 y x3 sólo se verían en los ámbitos en que se han definido.
Hay que resaltar que hemos especificado que la clase HolaMundo sea pública (public). Con esto cambiamos la accesibilidad de la clase. Podemos hacerla pública, de modo que todo el mundo pueda acceder a ella, sus propiedades y métodos. Podemos hacerla privada (private), que impide que nadie pueda acceder a la misma o protegida (protected), de modo que nadie excepto sus clases heredadas (ya veremos que significa esto) tienen permiso de acceso. Por defecto una clase permite el acceso sólo a miembros de su mismo paquete (package), que es el equivalente en Java a las librerías.
En este caso, podríamos haber dejado el valor de accesibilidad por defecto, pero conviene irse acostumbrando a hacer bien las cosas. Dado que en realidad queremos que la clase HolaMundo sea accesible al mundo exterior la haremos pública. Esto, sin embargo, tiene un par de restricciones que hay que tener en cuenta:
  1. Sólo puede haber una clase pública por fichero.
  2. El fichero debe llamarse igual que dicha clase pública.
Continuemos examinando el código fuente:
  public static void main(String[] args) {
  }
Con esto estamos definiendo un método de la clase HolaMundo. Un método con varias características especiales:
  1. Es público.
  2. Es estático. Esto significa que, por muchas instancias que creemos de la clase, existirá un único miembro de este tipo en la memoria. De hecho éste existe aunque no creemos instancias de la clase y, por tanto, se puede llamar.
  3. Como es un método (función) y no una propiedad (variable), debemos indicar el tipo del valor de retorno. En este caso será void, es decir, ninguno.
  4. El método se llama main. Este nombre es un nombre especial, ya que es el que deberá tener el método que se ejecute cuando se ejecute nuestra aplicación.
  5. Recibe como parámetro un vector de cadenas. Aunque en nuestro programa no lo vamos a utilizar es necesario que así sea, pues este vector recibe los parámetros que haya podido introducir el usuario al programa en la línea de comandos.
Por último, vamos a ver lo que realmente hace el programa:
    System.out.println("Hola, mundo");
Llamamos a una función del sistema. Entre las librerías que ofrece Java, existe una que se incluye por defecto en todos los programas, es java.lang. Entre las clases disponibles en la misma está la clase estática System, uno de cuyos miembros es out, uno de cuyos métodos esprintln. Navegando entre la ayuda de Java podemos comprobar todo esto. Este método recibe como parámetro una cadena que se encargará de escribir en pantalla.

Documentación

Java es un lenguaje cuya sintaxis es similar a C, C++ y Javascript. La explicaremos, por tanto, de forma rápida, empezando por los comentarios.
Los comentarios en Java son como los de C++ pero con añadidos. Tenemos el comentario que dura hasta final de línea:
// Esto es un comentario
Y el comentario que ocupa varias líneas:
/* Esto es un
comentario */
Sin embargo también tenemos un tipo especial de comentarios que procuran ayudar en la tarea de documentar nuestros programas. Todo programador sabe la pereza que da documentar nuestros programas, y por eso Java intenta ayudarnos con un tipo especial de comentarios. Es equivalente al anterior, pero se utiliza un asterisco más para empezar:
/** Esto es un comentario
que servirá para documentar nuestro programa */
La herramienta javadoc recogerá esos comentarios y los incluirá en una documentación en HTML que genera de forma automática. Dado que el resultado es HTML, se puede incluir HTML en los comentarios. Para respetar el modo de comentar de muchos programadores , un asterisco al comienzo de la línea será ignorado. También admite etiquetas especiales que comienzan por una arroba. Por ejemplo, nuestro anterior programa puede ser documentado de la siguiente forma:
HolaMundo.java
/** Este es el primer programa del Curso de Java
* Simplemente escribe la frase "Hola, mundo"
* @author Daniel Rodríguez
* @version 1.0
*/
public class HolaMundo {
/** Punto de entrada a la clase y la aplicación
* @param args vector de cadenas que contiene los argumentos
* @return No devuelve valor de retorno alguno
* @exception Ninguna No lanza excepciones
*/
  public static void main(String[] args) {
    System.out.println("Hola, mundo");
  }
}
Podemos ver el resultado escribiendo la siguiente línea:
javadoc -author -version HolaMundo.java
Y viendo con el navegador el HTML resultante.
Entre las etiquetas especiales, cabe destacar que se diferencian entre etiquetas de clase (@author y @version) y etiquetas de método (@param, @return, @exception y @deprecated). Este último requiere una atención especial. No existe un único Java, pues después de la primera versión se lanzaron la 1.1 y la 1.2 (también conocida como Java 2). Algunos aspectos del Java original comenzaron a parecer obsoletos y se decidió desaconsejar su uso, pues podrían dejar de ser soportados en el futuro. Se marcaron con la etiqueta deprecated. Así pues, se permitió utilizar esa etiqueta a los programadores de clases.
Llegados a este punto, puede ser necesario comenzar a distinguir entre dos tipos de programadores. Unos son los que se dedican a programar las librerías y las clases. Suelen ser más experimentados y estar mejor pagados, son los "programadores de clases". Otros, en cambio, recogen las clases que otros han programado y las unen para realizar aplicaciones; son los "consumidores de clases". Conviene saber cual es nuestra función, pues muchas cosas no resultan necesarias o, al menos, importantes para un consumidor como, por ejemplo, todo este asunto del deprecated.

Tipos de datos básicos

En Java todo lo que se mueve es un objeto... excepto los tipos de datos básicos, es decir, los números enteros, reales, los caracteres, los valores lógicos, etc.. Todo lo demás serán objetos o, mejor dicho, referencias a objetos. Son los únicos valores que se crean sin utilizar el operadornew, que veremos más adelante. Son los siguientes:
TipoDescripciónTamañoClase equivalente
booleanValor lógico1 bitBoolean
charCarácter16 bitCharacter
byteEntero muy pequeño8 bit
shortEntero pequeño16 bit
intEntero normal32 bitInteger
longEntero grande64 bitLong
floatNúmero real de precisión simple32 bitFloat
doubleNúmero real de doble precisión64 bitDouble
voidTipo vacío
Podemos crear variables de estos tipos de la manera normal en todos los lenguajes que siguen a C:
int numero = 12;
El definir un valor al declararlos no es necesario, especialmente porque en Java todos tienen un valor por defecto (0 en los números, false en los booleanos y el carácter número cero); en ese caso escribiríamos simplemente:
int numero;
En algunas ocasiones, que veremos en el futuro, es necesario utilizar clases que representen los mismos valores que los tipos de datos básicos. En esos casos utilizamos la clase equivalente. En nuestro ejemplo haríamos:
Integer numero = new Integer(12);

. Literales

Los literales son el mecanismo utilizado para expresar un valor constante. Java utiliza cinco tipos de elementos: enteros, reales en coma flotante, booleanos, caracteres y cadenas:
TipoEjemplo
Enteros21, 0xDC
Reales3.14, 2e12
Booleanostrue, false
Caracteres'x', \t, \u0234
Cadenas"Esto es una cadena"
Conviene fijarse en que, mientras los caracteres se expresan encerrados en comillas simples, las cadenas lo hacen en comillas dobles.

Otros elementos de sintaxis

Existen más pequeños ladrillos con los que se construye el lenguaje, que veremos a continuación.

. Identificadores

Un identificador es un nombre que le damos a un elemento, es decir, una clase, un método, una propiedad... Java distingue entre mayúsculas y minúsculas y los identificadores pueden comenzar por una letra, un subrayado o un símbolo de dolar, siendo los siguientes caracteres letras o dígitos. Por ejemplo, son identificadores válidos:
contador
_nombre_usuario
$dinero

. Operadores

Los operadores sirven para unir identificadores y literales para formar expresiones, que son el resultado de una operación. Los de Java son muy parecidos a los de C y C++.
Java dispone de los operadores aritméticos clásicos y algún que otro más:
DescripciónSímboloExpresión de ejemploResultado del ejemplo
Multiplicación*2*48
Resto de una división entera%5%21
Suma+2+24
Resta-7-25
Incremento++2++3
Decremento----21
Menos unario--(2+4)-6
Los operadores de incremento y decremento merecen una explicación auxiliar. Se pueden colocar tanto antes como después de la expresión que deseemos modificar pero sólo devuelven el valor modificado si están delante. Me explico.
a = 1;
b = ++a;
En este primer caso, a valdrá 2 y b 2 también. Sin embargo:
a = 1;
b = a++;
Ahora, a sigue valiendo 2, pero b es ahora 1. Es decir, estos operadores modifican siempre a su operando, pero si se colocan detrás del mismo se ejecutan después de todas las demás operaciones.
Normalmente los lenguajes tienen un único operador de asignación, que en Java es el símbolo=. Pero en este lenguaje, dicho operador se puede combinar con operadores aritméticos para dar los siguientes de modo que se realice una operación antes de asignar. Así, por ejemplo
a /= b
es equivalente a
a = a / b
Existen esos operadores combinados para la suma (+=), resta (-=), división (/=), multiplicación (*=) y resto (%=).
Los operadores de comparación toman dos cantidades numéricas y las comparan, devolviendo un valor lógico que será true si la comparación fue exitosa y false en caso contrario. Podemos usar los siguientes:
DescripciónSímboloExpresión de ejemploResultado del ejemplo
Igualdad==2==2true
Desigualdad!=2!==2false
Menor que<2<2false
Mayor que>3>2true
Menor o igual que<=2<=2true
Mayor o igual que>=1>=2false
Los operadores lógicos permiten evaluar expresiones lógicas complejas:
DescripciónSímboloExpresión de ejemploResultado del ejemplo
Negación!!(2==2)false
Y lógico&&(2 == 2) && (2 >= 0)true
O lógico||(2 == 2) || (2 != 2)true
Existen otros operadores La concatenación de cadenas, por ejemplo, se realiza con el símbolo +. El operador condicional tiene esta estructura:
condicion ? valor1 : valor2
Si la condición se cumple devuelve el primer valor y, en caso contrario, el segundo. El siguiente ejemplo asignaría a la variable a un 2:
int a = 2 > 3 ? 1 : 2
Como podéis ver no resulta muy legible. Huid de este operador como de la peste. Evitad la tentación. Procurad no usarlo. Yo he tenido que descifrar código escrito por mí un par de meses antes que tenía esta clase de operadores, a veces incluso anidados. Todavía tengo escalofríos al recordarlo.

. Separadores

Existen otros caracteres con significado especial en Java. Son los separadores:
SeparadorDescripción
()Contienen listas de parámetros, tanto en la definición de un método como en la llamada al mismo. También se utilizan para modificar la precedencia en una expresión, contener expresiones para control de flujo y realizar conversiones de tipo.
{}Se utilizan para definir bloques de código, definir ámbitos y contener los valores iniciales de los vectores.
[]Se utiliza tanto para declarar vectores o matrices como para referenciar valores dentro de los mismos.
;Separa sentencias.
,Separa identificadores consecutivos en la declaración de variables y en las listas de parámetros. También se utiliza para encadenar sentencias dentro de una estructura for.
.Separa un nombre de propiedad o método de una variable de referencia. También separa nombre de paquete de los de un subpaquete o una clase.

Control de flujo

El más básico, sencillo y utilizado es el if-else. Dado que else es opcional, se puede escribir de dos formas:
if (expresión booleana)
  sentencia
o
if (expresión booleana)
  sentencia
else
  sentencia
En general, cada vez que aparezca de ahora en adelante la palabra sentencia podrá significar dos cosas: una es una sentencia simple terminada en un punto y coma y otra es una serie de sentencias agrupadas mediante llaves. Esta construcción permite ejecutar la sentencia primera si la expresión lógica es verdadera. En caso de que sea falsa (y de que el if-else esté en la segunda forma) se ejecutará la segunda sentencia.
Para bifurcar también existe el switch:
switch (expresión) {
  case valor1: sentencia; break;
  case valor2: sentencia; break;
  case valor3: sentencia; break;
  // ...
  default: sentencia;
}
La estructura switch compara el valor contenido en la expresión con cada uno de los valores situados después del case, ejecutando en caso de que exista coincidencia el código situado a la derecha de los dos puntos. Ese código no se engloba mediante llaves. Si no existe coincidencia, se ejecuta la sentencia default.
Vamos a ver ahora las distintas estructuras que nos ofrece Java para realizar iteraciones. Las más genéricas son while y do-while:
while (expresión booleana)
  sentencia
y
do
  sentencia;
while (expresión booleana)
En ambos casos se ejecutará la sentencia mientras se cumpla la expresión booleana. La diferencia consiste en que en el primer caso esa expresión se comprueba antes de iterar (ejecutar la sentencia) y en el segundo después. Por tanto, en ese último caso, la sentencia se ejecutará al menos una vez.
Otra iteración interesante es el bucle for:
for (inicialización; expresión booleana; paso)
  sentencia
En este caso lo primero que hará será ejecutar las sentencias (separadas por comas) contenidas en inicialización, luego comprobará si es cierta la expresión booleana y ejecutará el código de sentencia. Finalmente ejecutará las sentencias (separadas por comas) contenidas en paso y comprobará de nuevo la expresión booleana.
Por último, conviene recordar el uso de return. Se utiliza para devolver un valor de retorno en los métodos (en caso de que éstos no devuelvan void). Pero también conviene destacar que en el momento que se llega a return, el programa sale del método que se esté ejecutando.

Constructores

Con lo que ya sabemos ya estamos preparados para entender y realizar ejemplos más complejos.
Rectangulo.java
/** Calcula el area de un rectangulo
*/
public class Rectangulo {
  private float x1,y1,x2,y2;
  public Rectangulo(float ex1, float ey1, float ex2, float ey2) {
    x1 = ex1;
    x2 = ex2;
    y1 = ey1;
    y2 = ey2;
  }
  public float calcularArea() {
    return (x2-x1) * (y2-y1);
  }
  public static void main(String[] args) {
    Rectangulo prueba = new Rectangulo(1,4,7,6);
    System.out.println(prueba.calcularArea());
  }
}
Seguimos incluyendo una sola clase en nuestro programa fuente, que guardaremos comoRectangulo.java y que compilaremos y ejecutaremos como vimos hace un par de capítulos. Veremos que nos responde con 12.0. Observando la clase que hemos creado, vemos que tiene cuatro propiedades, de tipo float, y tres métodos. Uno es el ya conocido main, otro parece un método normal y por último tenemos uno que se llama igual que la clase. Es un constructor.
Un constructor es el método que se llama cuando se crea una instancia nueva. Se suele utilizar para inicializar valores. Aquí vemos cómo se crea un objeto de tipo Rectangulo y se asigna a una referencia llamada prueba:
Rectangulo prueba = new Rectangulo(1,4,7,6);
Como vemos, al crear el objeto colocamos a la derecha de new una llamada al constructor. Si no especificamos ningún constructor, los objetos se crean un constructor por defecto que no admite parámetros y no realiza ninguna tarea de inicialización. Por ejemplo, si no hubiésemos especificado un constructor en nuestro ejemplo, podríamos haber creado un objeto rectángulo de este modo:
Rectangulo prueba = new Rectangulo();
En cuyo caso tendría todos sus propiedades en su valor inicial, es decir, cero. En el ejemplo vemos que recibe como parámetros las cuatro coordenadas necesarias para definir un rectángulo y las asigna a sus propiedades.
Finalmente, el programa realiza algo útil:
System.out.println(prueba.calcularArea());
Como vimos, esta llamada sirve para escribir en pantalla lo que le indiquemos. En este caso es una llamada al método de la clase Rectangulo llamado calcularArea(). Este método toma las coordenadas que alberga en las propiedades de la clase y a partir de ellas calcula el área según la fórmula geométrica debida.
Sin embargo, este método devuelve un valor de tipo float, es decir, no devuelve una cadena. ¿Cómo es que nos lo escribe por pantalla? Java lo convierte automáticamente en cadena cuando necesita una cadena. Esta pequeña violación en el estricto sistema de tipos de Java sólo se da con cadenas y más que nada por comodidad. En un futuro veremos de que manera podemos conseguir que nuestros objetos también se conviertan en cadenas de manera transparente.


Herencia

Si se supone que somos buenos programando, cuando creemos una clase es posible que sea algo útil. De modo que cuando estemos haciendo un programa distinto y necesitemos esa clase podremos incluirla en el código de ese nuevo programa. Es la manera más sencilla de reutilizar una clase.
También es posible que utilicemos esa clase incluyendo instancias de la misma en nuevas clases. A eso se le llama composición. Representa una relación "tiene un". Es decir, si tenemos una clase Rueda y una clase Coche, es de esperar que la clase Coche tenga cuatro instancias deRueda:
class Coche {
  Rueda rueda1, rueda2, rueda3, rueda 4;
  ...
}
Sin embargo, en ocasiones, necesitamos una relación entre clases algo más estrecha. Una relación del tipo "es un". Por ejemplo, sabemos bien que un gato es un mamífero. Sin embargo es también un concepto más específico, lo que significa que una clase Gato puede compartir con Mamifero propiedades y métodos, pero también puede tener algunas propias.
Herencia.java
class Mamifero {
  String especie, color;
}

class Gato extends Mamifero {
  int numero_patas;
}

public class Herencia {
  public static void main(String[] args) {
    Gato bisho;
    bisho = new Gato();
    bisho.numero_patas = 4;
    bisho.color = "Negro";
    System.out.println(bisho.color);
  }
}
Como vemos en el ejemplo, el objeto bisho no sólo tiene la propiedad numero_patas, también color que es una propiedad de Mamifero. Se dice que Mamifero es la clase padre y Gato la clase hija en una relación de herencia. Esta relación se consigue en Java por medio de la palabra reservada extends.
Pero, además de heredad la funcionalidad de la clase padre, una clase hija puede sobreescribirla. Podemos escribir un método en la clase hija que tenga el mismo nombre y los mismos parámetros que un método de la clase padre:
Herencia.java
class Mamifero {
  String especie, color;
  public void mover() {
    System.out.println("El mamífero se mueve");
  }
}

class Gato extends Mamifero {
  int numero_patas;
  public void mover() {
    System.out.println("El gato es el que se mueve");
  }
}

public class Herencia {
  public static void main(String[] args) {
    Gato bisho = new Gato();
    bisho.mover();
  }
}
Al ejecutar esta nueva versión veremos que se escribe el mensaje de la clase hija, no el del padre.
Conviene indicar que Java es una lenguaje en el que todas las clases son heredadas, aún cuando no se indique explícitamente. Hay una jerarquía de objetos única, lo que significa que existe una clase de la cual son hijas todas las demás. Este Adán se llama Object y, cuando no indicamos que nuestras clases hereden de nadie, heredan de él. Esto permite que todas las clases tengan algunas cosas en común, lo que permite que funcione, entre otras cosas, el recolector de basura.

Polimorfismo

En muchas ocasiones, cuando utilizamos herencia podemos terminar teniendo una familia de clases que comparten un interfaz común. Por ejemplo, si creamos un nuevo fichero que contenga a Mamifero y Gato le añadimos:
Polimorfismo.java
class Perro extends Mamifero {
  int numero_patas;
  public void mover() {
    System.out.println("Ahora es un perro el que se mueve");
  }
}

public class Polimorfismo {
  public static void muevete(Mamifero m) {
    m.mover();
  }
  public static void main(String[] args) {
    Gato bisho = new Gato();
    Perro feo = new Perro();
    muevete(bisho);
    muevete(feo);
  }
}
Vemos que el método muevete llama al método mover de un mamífero. El no sabe con qué clase de mamífero trata, pero la cosa funciona y se llama al método correspondiente al objeto específico que lo llama (es decir, primero un gato y luego un perro). Y esto no sólo se aplica a los métodos. Por ejemplo, podemos reescribir el código del procedimiento principal:
public static void main(String[] args) {
  Mamifero bisho = new Gato();
  muevete(bisho);
  bisho = new Perro();
  muevete(bisho);
}
Y vemos que funciona exactamente igual. El polimorfismo consiste en que toda referencia a un objeto de una clase específica puede tomar la forma de una referencia a un objeto de una clase heredada a la suya.

. Sobrecarga de métodos

El concepto de polimorfismo, en cuanto a cambio de forma, se puede extender a los métodos. Java permite que varios métodos dentro de una clase se llamen igual, siempre y cuando su lista de parámetros sea distinta. Por ejemplo, si tuviéramos un método que sirviera para sumar números pero sin indicar de qué tipo:
Sumar.java
/** Diversos modos de sumar */
public class Sumar {
  public float suma(float a, float b) {
    System.out.println("Estoy sumando reales");
    return a+b;
  }
  public int suma(int a, int b) {
    System.out.println("Estoy sumando enteros");
    return a+b;
  }
  public static void main(String[] args) {
    float x = 1, float y = 2;
    int v = 3, int w = 5;
    System.out.println(suma(x,y));
    System.out.println(suma(v,w));
  }
}
Esto también se aplica a los constructores. De hecho, es la aplicación más habitual de la sobrecarga:
Rectangulo.java
/** Calcula el area de un rectangulo
 */
public class Rectangulo {
  private float x1,y1,x2,y2;
  public Rectangulo(float ex1, float ey1, float ex2, float ey2) {
    x1 = ex1;
    x2 = ex2;
    y1 = ey1;
    y2 = ey2;
  }
  public Rectangulo() {
    x1 = 0;
    x2 = 0;
    y1 = 1;
    y2 = 1;
  }
  public float calcularArea() {
    return (x2-x1) * (y2-y1);
  }
  public static void main(String[] args) {
    Rectangulo prueba1 = new Rectangulo(1,4,7,6);
    Rectangulo prueba2 = new Rectangulo();
    System.out.println(prueba1.calcularArea());
    System.out.println(prueba2.calcularArea());
  }
}

0 comentarios:

Publicar un comentario

Share

Twitter Delicious Facebook Digg Stumbleupon Favorites