Java es un lenguaje de programación creado por Sun Microsystems para poder funcionar en distintos tipos de procesadores. Su sintaxis es muy parecida a la de C o C++, e incorpora como propias algunas características que en otros lenguajes son extensiones: gestión de hilos, ejecución remota, etc.
El código Java, una vez compilado, puede llevarse sin modificación alguna sobre cualquier máquina, y ejecutarlo. Esto se debe a que el código se ejecuta sobre una máquina hipotética o virtual, la Java Virtual Machine, que se encarga de interpretar el código (ficheros compilados .class) y convertirlo a código particular de la CPU que se esté utilizando (siempre que se soporte dicha máquina virtual).
Cuando se programa con Java, se dispone de antemano de un conjunto de clases ya implementadas. Estas clases (aparte de las que pueda hacer el usuario) forman parte del propio lenguaje (lo que se conoce como API (Application Programming Interface) de Java).
Para su correcto funcionamiento, Java necesita tener establecidas algunas variables de entorno: las variables PATH y CLASSPATH.
La variable de entorno del sistema PATH deberá contener la ruta donde se encuentren los programas para compilar y ejecutar (comandos javac y java de la distribución JDK de Sun, respectivamente). Por ejemplo:
set PATH=%PATH%;C:\jdk1.4\bin
Con la variable CLASSPATH indicamos dónde están las clases externas a las de la API que necesitemos para compilar o ejecutar nuestro programa. Cualquier clase necesaria que no pertenezca a la API debe estar incluída en el CLASSPATH para poder compilar o ejecutar (aunque se encuentre en el mismo directorio que la que compilamos).
Podemos incluir todas las clases que hay en un directorio (sin contar los subdirectorios) poniendo la ruta (absoluta o relativa al directorio actual) del directorio. Por ejemplo, si están en \misclases :
set CLASSPATH=%CLASSPATH%;C:\misclases
Si las clases pertenecen a un paquete concreto, se debe apuntar al directorio a partir del cual comienzan los directorios del paquete. Por ejemplo, si la clase MiClase está en el paquete unpaquete, dentro de \misclases (\misclases\unpaquete\MiClase.java):
set CLASSPATH=%CLASSPATH%;C:\misclases
Si las clases están empaquetadas en un fichero JAR (veremos a continuación qué es un fichero JAR), se tendrá que hacer referencia a dicho fichero. Por ejemplo:
set CLASSPATH=%CLASSPATH%;C:\misclases\misclases.jar
También podemos incluir en el CLASSPATH el directorio actual:
set CLASSPATH=%CLASSPATH%;.
La forma de establecer las variables cambia en función de la versión del sistema operativo. También podremos hacer estas variables permanentes modificando los ficheros de configuración oportunos (autoexec.bat, .profile, etc), o mediante el panel de control en algunas versiones de Windows.
Si queremos compilar una clase, se compila con el comando javac (deberemos asegurarnos de que dicho comando está accesible en el PATH), seguido del nombre de fichero a compilar:
javac NombreFichero.java
Tras haber compilado el ejemplo se tendrá un fichero NombreFichero.class, y se habrán compilado y actualizado (si no lo estaban ya) todas las clases que necesitara la clase compilada.
Una vez compilada la clase, para ejecutarla utilizamos el comando java seguido del nombre de la clase (sin extensión):
java NombreClase
Si se quisieran pasar parámetros al programa, se pasan después de la clase:
java NombreClase 20 56 Hola
También podemos ejecutar un fichero JAR, si contiene una clase principal. Para ello pondremos:
java -jar Fichero.jar
A la hora de compilar y ejecutar, es importante respetar las mayúsculas y minúsculas de los nombres de ficheros y clases, y asegurarnos antes de compilar o ejecutar que el CLASSPATH está correctamente establecido para encontrar todas las clases necesarias.
Java dispone de una utilidad que permite empaquetar varias clases en un solo fichero comprimido, de forma que hacemos al conjunto más portable, y se puede acceder a las clases de la misma forma que si estuvieran sueltas en el disco. Estos ficheros comprimidos tienen una extensión .jar, y su comportamiento es similar al de un fichero ZIP o un fichero TAR.
Los ficheros JAR tienen varias ventajas:
Los ficheros JAR utilizan el formato ZIP, por lo que podremos abrirlos con cualquier aplicación que trabaje con ficheros ZIP. De todas formas Java incorpora una herramienta para trabajar con estos ficheros JAR, llamada jar. Con ella podremos empaquetar o extraer el contenido de un fichero JAR, y realizar otras operaciones propias de este formato. Su uso y sintaxis es similar al comando TAR de Linux. Algunos ejemplos:
jar cvf miFichero.jar aaa/*.class
--> Empaqueta en miFichero.jar todos los .class de la carpeta aaa y subcarpetas
jar tf miFichero.jar
--> Muestra el contenido de miFichero.jar (qué archivo(s) contiene)
jar xvf miFichero.jar
--> Extrae los ficheros de miFichero.jar, respetando directorios y subdirectorios
A partir de JDK 1.2 se incorpora un mecanismo de extensiones que permite añadir nuevas funcionalidades al núcleo de la plataforma Java. Existen extensiones desarrolladas por Sun, como Java 3D y Javamail, pero además cualquier usuario puede desarrollar sus propias extensiones.
El mecanismo de extensiones proporciona escalabilidad a Java, permitiendo hacer que nuestra propia API esté disponible para cualquier aplicación.
Con lo visto hasta ahora, cuando intentemos compilar o ejecutar un programa, Java buscará las clases necesarias en el siguiente orden:
Eclipse es una herramienta que permite integrar diferentes tipos de aplicaciones. La aplicación principal es el JDT (Java Development Tooling), un IDE para crear programas en Java. Otras aplicaciones, que no vienen con la distribución estándar de Eclipse, se añaden al mismo en forma de plugins, y son reconocidos automáticamente por la plataforma.
Además, Eclipse tiene su propio mecanismo de gestión de recursos. Los recursos son ficheros en el disco duro, que se encuentran alojados en un espacio de trabajo (workspace), un directorio especial en el sistema. Así, si una aplicación de Eclipse modifica un recurso, dicho cambio es notificado al resto de aplicaciones de Eclipse, para que lo tengan en cuenta.
Para instalar Eclipse se requiere:
Para la instalación, se siguen los pasos:
eclipse -vm ruta_jdk_jre
Para arrancar Eclipse se tiene el ejecutable eclipse.exe. La pantalla inicial de Eclipse aparecerá tras unos segundos:
Veremos las opciones principales con detalle más adelante. De los menús, entre otros, pueden resultar interesantes:
El usuario trabaja con Eclipse mediante el entorno gráfico que se le presenta. Según la perspectiva que elija, se establecerá la apariencia de dicho entorno. Entendemos por perspectiva una colección de vistas y editores, con sus correspondientes acciones especiales en menús y barras de herramientas. Algunas vistas muestran información especial sobre los recursos, y dependiendo de las mismas, en ocasiones sólo se mostrarán algunas partes o relaciones internas de dichos recursos. Un editor trabaja directamente sobre un recurso, y sólo cuando grabe los cambios sobre el recurso se notificará al resto de aplicaciones de Eclipse sobre estos cambios. Las vistas especiales se pueden conectar a editores (no a recursos), por ejemplo, la vista de estructura (outline view) se puede conectar al editor Java. De este modo, una de las características importantes de Eclipse es la flexibilidad para combinar vistas y editores.
Si queremos abrir una determinada perspectiva, vamos a Window -> Open Perspective. Eligiendo luego Other podemos elegir entre todas las perspectivas disponibles:
Para añadir vistas a una perspectiva, primero abrimos la perspectiva, y luego vamos a Window -> Show View y elegimos la que queramos cargar:
Arrastrando la barra de título de una vista o editor, podemos moverlo a otro lugar de la ventana (lo podremos colocar en las zonas donde el cursor del ratón cambie a una flecha negra), o tabularlo con otras vistas o editores (arrastrando hasta el título de dicha vista o editor, el cursor cambia de aspecto, y se ve como una lista de carpetas, soltando ahí la vista o editor que arrastramos, se tabula con la(s) que hay donde hemos soltado).
Desde el menú Window - Preferences podemos establecer opciones de configuración de los distintos aspectos de Eclipse:
Establecer directorios para ficheros fuente o ficheros objeto
Podemos elegir entre tener nuestro código fuente en el mismo lugar que nuestras clases objeto compiladas, o bien elegir directorios diferentes para fuentes y objetos. Para ello tenemos, dentro del menú de configuración anterior, la opción Java - New Project. En el cuadro Source and output folder podremos indicar si queremos colocarlo todo junto (marcando Project) o indicar un directorio para cada cosa (marcando Folders, y eligiendo el subdirectorio adecuado para cada uno):
Establecer la versión de JDK o JRE
Para cambiar el compilador a una versión concreta de Java, elegimos la opción de Java y luego Compiler. Pulsamos en la pestaña Compliance and Classfiles y elegimos la opción 1.4 (o la que sea) de la lista Compiler compliance level:
También podemos utilizar JDK en lugar de JRE para ejecutar los programas. Para ello vamos a Java - Installed JREs, elegimos la línea Standard VM y pulsamos en Edit o en Add, según si queremos modificar el que haya establecido, o añadir nuevas opciones.
Se nos abre un cuadro de diálogo para editar valores. Pulsando en Browse elegimos el directorio de JDK (por ejemplo, C:\j2sdk1.4.0).
Especificar variables de entorno (CLASSPATH)
Podemos añadir variables de entorno en Eclipse, cada una conteniendo un directorio, fichero JAR o fichero ZIP. Para añadir variables vamos a la opción Java - Classpath Variables.
Pulsamos el botón de New para añadir una nueva, y le damos un nombre, y elegimos el fichero JAR o ZIP (pulsando en File) o el directorio (pulsando en Folder).
Espacio de trabajo
Por defecto el espacio de trabajo (workspace) para Eclipse es el directorio ECLIPSE_HOME/workspace. Podemos elegir un directorio arbitrario lanzando eclipse con una opción -data que indique cuál es ese directorio, por ejemplo:
eclipse -data C:\misTrabajos
Si no indicamos el espacio de trabajo mediante esta opción, Eclipse nos preguntará qué espacio de trabajo queremos utilizar en la siguiente ventana:
También podemos crear nuestros proyectos y trabajos fuera del workspace si queremos, podemos tomarlo simplemente como un directorio opcional donde organizar nuestros proyectos.
Nuevo proyecto
Lo primero que debemos hacer para empezar a desarrollar una nueva aplicación es crear un proyecto Java en Eclipse. Para ello seleccionamos la opción del menú File > New > Project ... Dentro de la ventana de nuevo proyecto, seleccionamos Java Project y pulsamos Next.
En la siguiente pantalla deberemos dar un nombre al proyecto para identificarlo dentro de Eclipse. Por defecto creará el directorio para este proyecto dentro del espacio de trabajo de Eclipse. Si queremos crearlo en otro directorio desmarcaremos la casilla Use default.
Cuando hayamos introducido esta información pulsamos sobre Next.
En la siguiente pantalla, podremos configurar varias cosas:
Cuando terminemos con los cambios, pulsamos en Finish para terminar de crear el proyecto.
Crear directorios y ficheros
Una vez creado el proyecto, podremos crear directorios y ficheros dentro de nuestro directorio de desarrollo. Por ejemplo, si queremos crear una web para nuestro proyecto, podremos crear un directorio web donde guardaremos los documentos HTML.
Pulsando con el botón derecho sobre nuestro proyecto en el explorador de paquetes se abrirá un menú contextual con el que podremos crear un nuevo directorio. Para ello pulsaremos sobre la opción New y veremos todos los elementos que podremos crear desde el lugar donde pinchamos (carpetas, ficheros, clases Java, paquetes, etc) .
Para instalar nuevos plugins que aporten nuevas funcionalidades a Eclipse (por ejemplo, diseño de diagramas UML, resalte de sintaxis en ficheros HTML o JSP, plugins de desarrollo J2EE, etc) simplemente hay que, una vez obtenidos, copiarlos (descomprimirlos, si es el caso) en el directorio ECLIPSE_HOME/plugins. Después habrá que reiniciar Eclipse para que pueda tomar los nuevos plugins instalados.
En un programa Java podemos distinguir varios elementos:
Para definir una clase se utiliza la palabra reservada class, seguida del nombre de la clase:
class MiClase { ... }
Dentro de una clase, o de un método, podemos definir campos o variables, respectivamente, que pueden ser de tipos simples, o clases complejas, bien de la API de Java, bien que hayamos definido nosotros mismos, o bien que hayamos copiado de otro lugar.
int a; // Tipo simple entero Vector v; // Clase compleja propia de Java MiOtraClase mc; // Clase compleja nuestra
Los métodos o funciones se definen de forma similar a como se hacen en C: indicando el tipo de datos que devuelven, el nombre del método, y luego los argumentos entre paréntesis:
void imprimir(String mensaje) { ... // Código del método } Vector insertarVector(Object elemento, int posicion) { ... // Código del método }
Podemos interpretar los constructores como métodos que se llaman igual que la clase, y que se ejecutan con el operador new para reservar memoria para los objetos que se creen de dicha clase:
MiClase() { ... // Código del constructor } MiClase(int valorA, Vector valorV) { ... // Código de otro constructor }
No tenemos que preocuparnos de liberar la memoria del objeto al dejar de utilizarlo. Esto lo hace automáticamente el garbage collector. Aún así, podemos usar el método finalize() para liberar manualmente.
Las clases en Java se organizan (o pueden organizarse) en paquetes, de forma que cada paquete contenga un conjunto de clases. También puede haber subpaquetes especializados dentro de un paquete o subpaquete, formando así una jerarquía de paquetes, que después se plasma en el disco duro en una estructura de directorios y subdirectorios igual a la de paquetes y subpaquetes (cada clase irá en el directorio/subdirectorio correspondiente a su paquete/subpaquete).
Cuando queremos indicar que una clase pertenece a un determinado paquete o subpaquete, se coloca al principio del fichero la palabra reservada package seguida por los paquetes/subpaquetes, separados por '.' :
package paq1.subpaq1; ... class MiClase { ...
Si queremos desde otra clase utilizar una clase de un paquete o subpaquete determinado (diferente al de la clase en la que estamos), incluimos una sentencia import antes de la clase (y después de la línea package que pueda tener la clase, si la tiene), indicando qué paquete o subpaquete queremos importar:
import paq1.subpaq1.*;
import paq1.subpaq1.MiClase;
La primera opción (*) se utiliza para importar todas las clases del paquete (se utiliza cuando queremos utilizar muchas clases del paquete, para no ir importando una a una). La segunda opción se utiliza para importar una clase en concreto.
Al importar, ya podemos utilizar el nombre de la clase importada directamente en la clase que estamos construyendo. Si no colocásemos el import podríamos utilizar la clase igual, pero al referenciar su nombre tendríamos que ponerlo completo, con paquetes y subpaquetes:
// Si hemos hecho el 'import' antes MiClase mc; // Si NO hemos hecho el 'import' antes paq1.subpaq1.MiClase mc;
Existe un paquete en la API de Java, llamado java.lang, que no es necesario importar. Todas las clases que contiene dicho paquete son directamente utilizables. Para el resto de paquetes (bien sean de la API o nuestros propios), será necesario importarlos cuando los necesitemos desde una clase fuera de dichos paquetes.
Aunque para una clase simple o un programa de uso interno sencillo no es necesario agrupar las clases en paquetes, sí es recomendable asignar un nombre de paquete a cada clase de una aplicación, para evitar que luego Java no pueda encontrarlas, debido a que no tienen paquete asignado.
Tanto las clases como los campos y métodos admiten modificadores de acceso, para indicar si dichos elementos tienen ámbito público, protegido o privado. Dichos modificadores se marcan con las palabras reservadas public, protected y private, respectivamente, y se colocan al principio de la declaración:
public class MiClase { ... protected int b; ... private int miMetodo(int b) { ...
El modificador protected implica que los elementos que lo llevan son visibles desde la clase, sus subclases, y las demás clases del mismo paquete que la clase.
Si no se especifica ningún modificador, el elemento será considerado de tipo paquete. Este tipo de elementos podrán ser visibles desde la clase o desde clases del mismo paquete.
Cada fichero Java que creemos debe tener una y sólo una clase pública (que será la clase principal del fichero). Dicha clase debe llamarse igual que el fichero. Aparte, el fichero podrá tener otras clases internas, pero ya no podrán ser públicas.
Por ejemplo, si tenemos un fichero MiClase.java, podría tener esta apariencia:
public class MiClase { ... } class OtraClase { ... } class UnaClaseMas { ... }
Además de los modificadores de acceso vistos antes, en clases, métodos y/o campos se pueden utilizar también estos modificadores:
Estos modificadores se colocan tras los modificadores de acceso:
public abstract class Ejemplo // Clase abstracta para heredar de ella { public static final TAM = 10; // Constante estática de valor 10 public abstract void metodo(); // Método abstracto a implementar public synchronized void otroMetodo() { ... // Aquí dentro sólo puede haber un hilo a la vez } }
NOTA IMPORTANTE: si tenemos un método estático (static), dentro de él sólo podremos utilizar elementos estáticos (campos o métodos estáticos), o bien campos y métodos de objetos que hayamos creado dentro del método. Por ejemplo, si tenemos:
public class UnaClase { public int a; public static int metodo() { return a + 1; } }
dará error, porque el campo a no es estático, y lo estamos utilizando dentro del método estático. Para solucionarlo tenemos dos posibilidades: definir a como estático (si el diseño del programa lo permite), o bien crear un objeto de tipo UnaClase en el método, y utilizar su campo a (que ya no hará falta que sea estático, porque hemos creado un objeto y ya podemos acceder a su campo a):
public class UnaClase { public int a; public static int metodo() { UnaClase uc = new UnaClase(); // ... Aquí haríamos que uc.a tuviese el valor adecuado return uc.a + 1; } }
En las clases principales de una aplicación (las clases que queramos ejecutar) debe haber un método main con la siguiente estructura:
public static void main(String[] args) { ... // Código del método }
Dentro pondremos el código que queramos ejecutar desde esa clase. Hay que tener en cuenta que main es estático, con lo que dentro sólo podremos utilizar campos y métodos estáticos, o bien campos y métodos de objetos que creemos dentro del main.
Con todo lo anterior, podríamos tener una clase como:
package paquete1.subpaquete1; import otropaquete.MiOtraClase; import java.util.Vector; public class MiClase { public int a; public Vector v; private MiOtraClase mc; public MiClase() { ... // Código del constructor } public MiClase(int valorA, Vector valorV) { ... // Código de otro constructor } void imprimir(String mensaje) { ... // Código del método } public Vector insertarVector(Object elemento, int posicion) { ... // Código del método } }
Y podríamos definir una instancia de esa clase, y utilizar sus campos y métodos. Para ello utilizamos el operador new:
import paquete1.subpaquete1.*; import java.util.*; public class OtraClase { public void metodo() { MiClase mc; mc = new MiClase (1, new Vector()); mc.a++; mc.insertarVector("hola", 0); } }
Cuando queremos que una clase herede de otra, se utiliza al declararla la palabra extends tras el nombre de la clase, para decir de qué clase se hereda. Para hacer que Pato herede de Animal:
class Pato extends Animal
Con esto automáticamente Pato tomaría todo lo que tuviese Animal (aparte, Pato puede añadir sus características propias). Si Animal fuese una clase abstracta, Pato debería implementar los métodos abstractos que tuviese.
this se usa para hacer referencia a los miembros de la propia clase. Se utiliza cuando hay otros elementos con el mismo nombre, para distinguir :
public class MiClase { int i; public MiClase (int i) { this.i = i; // i de la clase = parametro i } }
super se usa para llamar al mismo elemento en la clase padre. Si la clase MiClase tiene un método Suma_a_i(...), lo llamaríamos desde esta otra clase con:
public class MiNuevaClase extends MiClase { public void Suma_a_i (int j) { i = i + (j / 2); super.Suma_a_i (j); } }
Ya hemos visto cómo definir clases normales, y clases abstractas. Si queremos definir un interfaz, se utiliza la palabra reservada interface, en lugar de class, y dentro declaramos (no implementamos), los métodos que queremos que tenga la interfaz:
public interface MiInterfaz { public void metodoInterfaz(); public float otroMetodoInterfaz(); }
Después, para que una clase implemente los métodos de esta interfaz, se utiliza la palabra reservada implements tras el nombre de la clase:
public class UnaClase implements MiInterfaz { public void metodoInterfaz() { ... // Código del método } public float otroMetodoInterfaz() { ... // Código del método } }
Notar que si en lugar de poner implements ponemos extends, en ese caso UnaClase debería ser un interfaz, que heredaría del interfaz MiInterfaz para definir más métodos, pero no para implementar los que tiene la interfaz. Esto se utilizaría para definir interfaces partiendo de un interfaz base, para añadir más métodos a implementar.
Una clase puede heredar sólo de otra única clase, pero puede implementar cuantos interfaces necesite:
public class UnaClase extends MiClase implements MiInterfaz, MiInterfaz2, MiInterfaz3 { ... }
Veamos ahora algunos ejemplos de programas en Java.
public class Ejemplo1 { public static void main(String[] args) { System.out.println ("Mi programa Java"); } }
public class Ejemplo2 { int n1; // Primer numero (el entero) float n2; // Segundo numero (el real) /* Constructor */ public Ejemplo2(int n1, float n2) { this.n1 = n1; this.n2 = n2; } /* Suma dos numeros y devuelve el resultado (real) */ public float suma() { return (n1 + n2); } /* Main */ public static void main(String[] args) { if (args.length < 2) { System.out.println ("Uso: java Ejemplo2 <n1> <n2>"); System.exit(-1); } // Tomamos los dos parametros int n1 = Integer.parseInt(args[0]); float n2 = Float.parseFloat(args[1]); // Creamos un objeto Ejemplo2 y le pedimos la suma de los valores Ejemplo2 e = new Ejemplo2(n1, n2); System.out.println ("Resultado: " + e.suma()); } }
/* Este ejemplo resuelve el teorema de Pitágoras: hipotenusa = raiz (cateto1 * cateto1 + cateto2 * cateto2) */ public class Ejemplo3 { // Primer Cateto public static final int CATETO1 = 20; // Segundo Cateto public static final int CATETO2 = 50; /* Obtiene la hipotenusa de dos catetos que se pasan como parametro */ public static double hipotenusa(int cateto1, int cateto2) { return Math.sqrt(Math.pow(cateto1, 2) + Math.pow(cateto2, 2)); } /* Main */ public static void main(String[] args) { System.out.println ("La hipotenusa de los catetos indicados es:"); double h = hipotenusa(CATETO1, CATETO2); System.out.println ("h = " + h); } }
/* Este ejemplo devuelve los numeros primos encontrados hasta un cierto valor */ public class Ejemplo4 { /* Obtiene si un número es primo o no */ public static boolean esPrimo (int valor) { int i = 2; while (i < valor) { if (valor % i == 0) return false; i++; } return true; } /* Main */ public static void main(String[] args) { System.out.println ("Numeros primos hasta el " + args[0] + ":"); for (int i = 1; i < Integer.parseInt(args[0]); i++) if (esPrimo(i)) System.out.print (" " + i); System.out.println ("\nFinalizado"); } }
/** * Ejemplo de herencias y clases abstractas */ public class Ejemplo5 { /* Main */ public static void main(String[] args) { Hombre h = new Hombre(); Anciano a = new Anciano(); Persona p = (Persona)a; System.out.println ("Edad del hombre: " + h.edad()); System.out.println ("Genero del anciano: " + a.genero()); System.out.println ("Clase de la persona: " + p.clase()); } }
/** * Ejemplo de herencias y clases abstractas */ public abstract class Persona { /* Devuelve la clase a la que pertenecen las personas */ public String clase() { return "mamiferos"; } /* Devuelve el genero de la persona */ public abstract String genero(); /* Devuelve la edad de la persona */ public abstract String edad(); }
/** * Ejemplo de herencias y clases abstractas */ public class Hombre extends Persona { /* No hace falta definir el metodo clase(), porque ya esta definido en la clase padre. Lo tendríamos que definir si queremos devolver algo distinto a lo que devuelve allí */ /* Devuelve el genero de la persona (este metodo si hay que definirlo porque es abstracto en la clase padre) */ public String genero() { return "masculino"; } /* Devuelve la edad de la persona (este metodo si hay que definirlo porque es abstracto en la clase padre) */ public String edad() { return "40"; } }
/** * Ejemplo de herencias y clases abstractas */ public class Anciano extends Hombre { /* No hace falta definir ningun metodo, sólo aquellos en los que queramos devolver cosas distintas. En este caso, la edad */ /* Devuelve la edad de la persona */ public String edad() { return "75"; } }