Programación en Java SE 6 desde cero: Parte 7
Fundamentos de Programación
Objetivos:
Utilizar apropiadamente modificadores de acceso en las clases, declaraciones de paquetes y sentencias import.
Determinar el comportamiento esperado al ejecutar las clases desde línea de comandos.
Determinar el efecto sobre referencias a objetos y valores primitivos cuando son pasados como argumentos a métodos y modificados dentro de dichos métodos.
Reconocer el momento a partir del cual un objeto se convierte en elegible para ser recolectado por el recolector de basura de la JVM.
Reconocer el comportamiento de System.gc() y Object.finalize().
Aplicar correctamente los operadores apropiados para asignación, operaciones aritméticas, relacionales, operador instanceof, operadores lógicos y operadores condicionales.
Determinar la igualdad de dos objetos, de dos referencias a objetos o de dos variables de tipo primitivo.
Interacción del código en JAVA
Paquetes en JAVA
Un paquete es un grupo de clases e interfacez afines (en funcionalidad, en uso, en relaciones entre ella etc).
Los paquetes nos ayudan a organizar y a estrucuturar nuestras aplicaciones, y por otro lado, crean un espacio de nombrado para nuestros componentes, en el sentido de que nos permiten tener un nombre único para todo componente de nuestra aplicación. Por ejemplo, si ClaseA se encuentra en el paquete com.mipaquete, el nombre oficial de nuestra clase será com.mipaquete.ClaseA y este nombre es único en toda nuestra aplicación. A este nombre se le conoce como nombre completo cualificado de la clase.
Package en JAVA
Para indicar que una clase pertenece a un paquete determinado, se utiliza la palabra clave package como primera línea del código fuente.
El código compilado de una clase (.class) que pertenece a un paquete determinado, estará obligatoriamente en una estructura de directorios en filesystem que refleja exactamente la estructura del nombre del paquete.
Por ejemplo, si ClaseA pertenece al paquete com.mipaquete1.mipaquete2, la ubicación de ClaseA.class en filesystem sería: com/mipaquete1/mipaquete2.
En el siguiente capítulo abordaremos esto en más detalle.
Es importante tener en cuenta que si una clase/interface no especifica que pertenece a algún paquete (es decir, no hay sentencia package en su código fuente), entonces esa clase pertenece al paquete sin nombre. Es una especie de paquete por defecto proporcionado por la JVM, cuyo directorio en filesystem es “.”.
¿Es correcto el siguiente código fuente?
/* Esta clase es de prueba*/
package com.mipaquete;
class ClaseA {
}
Para responder, debe tenerse en cuenta que cuando se dice que package es la primera línea de código fuente, no se tienen en cuenta los comentarios. Por tanto, el código anterior es correcto.
Import en JAVA
La sentencia import se utiliza para importar en el código fuente bien una clase o bien un paquete completo. Los import tienen que aparecer después de la palabra clave package (si existe) y antes del class de la clase que está realizando las importaciones:
/* Importar todas las clases de com.mipaquete1 */
import com.mipaquete1.*
/* Importar sólo la clase ClaseA de com.mipaquete1 */
import com.mipaquete1.ClaseA
La presencia de import no afecta al bytecode generado, es decir, no tiene ningún tipo de connotación en los class generados. En realidad, el término “importar” un paquete o una clase puede resultar “engañoso”, porque se refiere únicamente a indicar al compilador que nos vamos a referir a las clases del paquete importado o a la clase importada sin utilizar el nombre completo cualificado, pero no añade nada al código class generado.
El paquete java.lang es automáticamente importado por el compilador, de manera transparente para nosotros. Este paquete contiene muchas de las clases básicas (String, Object, Integer, etc), por eso para utilizarlas no necesitamos referirnos a ellas como java.lang.String por ejemplo.
Supongamos que ClaseA pertenece al paquete com.mipaquete1:
package com.mipaquete1;
public class ClaseA {
………
}
Si desde ClaseB queremos crear una instancia de ClaseA, hay que recordar que ahora su nombre completo es com.mipaquete1.ClaseA:
public class ClaseB {
public void metodo() {
….
ClaseA instanciaA = new ClaseA();
/* la línea anterior no compila, porque ClaseA a secas no existe */
com.mipaquete1.ClaseA instanciaA = new com.mipaquete1.ClaseA();
/* la línea anterior es lo CORRECTO */
…..
}
}
¿Cómo quedaría el código si usamos import?
import com.mipaquete1.ClaseA;
public class ClaseB {
public void metodo() {
….
ClaseA instanciaA = new ClaseA();
…..
}
}
Utilizando import, nuestro ejemplo quedaría como sigue:
/* ClaseB pertenece al paquete com.mipaquete2 */
package com.mipaquete2;
/* Importamos la ClaseA para poder instanciarla sin poner su nombre cualificado completo*/
import com.mipaquete1.ClaseA;
public class ClaseB {
public void metodo() {
….
ClaseA instanciaA = new ClaseA(); // Ahora sí es correcto
…..
}
}
Imaginemos la siguiente clase ClaseC que se encuentra en el mismo paquete que ClaseA:
package com.mipaquete1
public class ClaseC {
………
}
Si quisiésemos utilizar desde ClaseB a las clases ClaseA y ClaseC, podríamos importar el paquete completo:
/* ClaseB pertenece al paquete com.mipaquete2 */
package com.mipaquete2;
import com.mipaquete1.*;
public class ClaseB {
public void metodo() {
….
ClaseA instanciaA = new ClaseA();
ClaseC instanciaC = new ClaseC();
…..
}
}
Utilizando import, nuestro ejemplo quedaría como sigue:
/* ClaseB pertenece al paquete com.mipaquete2 */
package com.mipaquete2;
/* Importamos la ClaseA para poder instanciarla sin poner su nombre cualificado completo*/
import com.mipaquete1.ClaseA;
public class ClaseB {
public void metodo() {
….
ClaseA instanciaA = new ClaseA(); // Ahora sí es correcto
…..
}
}
Jars: empaquetamiento de clases
Los paquetes pueden estar en filesystems o bien empaquetados en ficheros jars.
Como ya hemos visto, el paquete com.mipaquete1 existirá en filesystem como com/mipaquete1. Pero también lo podemos empaquetar en un fichero jar con el comando jar.
Supongamos que nos posicionamos una carpeta por encima de com/mipaquete1 y com/mipaquete2 y ejecutamos el comando siguiente (de jdk):
jar cf paquete.jar com
Dentro de paquete.jar estarían las estructuras de directorios com/mipaquete1 y com/mipaquete2 con todas sus clases.
La opción t nos muestra el contenido del paquete:
jar tf paquete.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/mipaquete1
com/mipaquete1/ClaseA.class
com/mipaquete2
com/mipaquete2/ClaseB.class
Modificadores de Acceso en JAVA
Los modificadores son elementos del lenguaje (palabras reservadas) que se colocan delante de la definición de propiedades, métodos o clases cuyo objetivo es indicar la visibilidad de dicho elemento en la aplicación, es decir, responden a la pregunta ¿Qué componentes de la aplicación pueden acceder a este elemento (propiedad, método o clase)?
Aqui la palabra acceder hay que contextualizarla. Si el elemento es una propiedad, acceder significa utilizar la propiedad directamente, si es un método, acceder significa invocarle, y si es una clase, acceder significa instanciarla.
Existen los siguiente modificadores de acceso:
Public: Indica que cualquiera puede acceder al elemento (propiedad, método o clase).
Package com.mipaquete1;
public ClaseA {
private int edad;
private int numero;
protected String nombre;
String dni;
public int getEdad() {
}
}
Private: Sólo se puede acceder al elemento desde métodos de la propia clase. Este modificador no existe para clases, es sólo para propiedades y métodos.
Protected: Sólo se puede acceder al elemento desde métodos de la propia clase, o bien desde métodos de clases del mismo paquete, o bien desde cualquier método de clases hijas. Este modificador no existe para clases, es sólo para propiedades y métodos.
Sin modificador (paquete): Sólo se puede acceder al elemento desde métodos de la propia clase o bien desde métodos de cualquier clase del mismo paquete.
Modificadores de Acceso en clases JAVA
El código fuente de toda clase Java debe ser definido en un fichero texto con extensión .java. Si la clase es declarada public, el nombre del fichero .java que la contiene debe coincidir con el nombre de dicha clase. Esto implica que en un fichero .java sólo puede haber una única clase declarada como public.
Teniendo esto en cuenta, ¿podrías identificar los ficheros resultantes de la compilación del siguiente .java?
public class ClaseA {
}
public class ClaseB {
}
Como habrás podido deducir, el código anterior es incorrecto, ya que sólo puede existir una única clase public en un fichero java, y además, los nombres de dicha clase pública y del fichero java tienen que coincidir.
Cuando se compila un fichero .java, se generan varios ficheros .class, uno por cada clase definida en el .java. Es decir, que podemos tener el código fuente de varias clases en un mismo fichero .java. Los ficheros .class no constituyen código ejecutable, sino que están en formato bytecode. Este código (bytecode) es interpretado por la máquina virtual Java al ejecutar la clase.
Teniendo esto en cuenta, ¿podrías identificar los ficheros resultantes de la compilación del siguiente .java?
public class ClaseA {
}
class ClaseB {
}
Como resultado de la compilación del java
public class ClaseA {
}
class ClaseB {
}
se generan los ficheros ClaseA.class y ClaseB.class.
Las clases únicamente admiten dos modificadores de acceso: public y package (modificador por defecto que no tiene palabra clave asociada, así que cuando se quiere especificar package no se especifica ningún modificador de acceso).
public class ClaseA → definición de clase pública, accesible (se puede instanciar) desde cualquier otra clase.
class ClaseB → definición de clase con acceso a nivel de paquete, accesible sólo desde el mismo paquete, modificador por defecto.
Ejecución Runtime, Classpath y Estructura Jar en JAVA
El punto de entrada de todo programa Java es un método main que podemos definir en cualquier clase.
public static void main(String [] args)
También puede ser:
static public void main(String [] args)
En Java 5.0, un método puede declarar una lista de argumentos de longitud variable con “…”, así que la signatura del método main en Java 5 también podría ser:
public static void main(String… args)
Observa el código fuente siguiente:
public class ClaseA {
public static void main() {
System.out.println(“HOLA”);
}
}
¿Consideras que compilará correctamente?
¿Crees que define correctamente un método main que constituya un punto de entrada de un programa Java?
Observa nuevamente el código:
public class ClaseA {
public static void main() {
System.out.println(“HOLA”);
}
}
Aunque la clase compila correctamente, el método main implementado no constituye un punto de entrada de la aplicación.
Lo sería si la declaración del método fuera, por ejemplo:
public static void main(String[] args)
El método main debe tener como argumentos un array de String.
Los argumentos del método main son los parámetros que la ejecución recibe por línea de comandos, comenzando, como todo array en Java, desde el índice 0.
package com.mipaquete1;
public class ClaseA {
public static void main(String[] args) {
System.out.println("Hola desde ClaseA");
System.out.println("Número de argumentos: " + args.length);
for (int i = 0; i<args.length; i++) {
System.out.println("Argumento: " + args[i]);
}
}
}
Al compilar esta clase, recordemos que el .class se crea en la estructura com/mipaquete1/ClaseA.class.
Para ejecutarlo, primero nos posicionamos en la carpeta padre del directorio com:
> cd <directorio_padre_de_com>
> dir
12/03/2011 10:49 <DIR> com
> java com.mipaquete1.ClaseA arg1 arg2
Hola desde ClaseA
Número de argumentos: 2
Argumento: arg1
Argumento: arg2
No olvides que al ejecutar no se pone la extensión .class.
¿Qué sucedería si en el anterior ejemplo ejecutamos la clase ClaseA (entiéndase su método main) , posicionados igual que en el ejemplo (en la carpeta padre de com), pero ejecutando de la siguiente manera?
> cd
> dir
12/03/2011 10:49 <DIR> com
>java mipaquete1.ClaseA arg1 arg2
¿Y si nos posicionamos dentro de com y ejecutamos “java mipaquete1.ClaseA arg1 arg2”?
En el primero de los casos, obtendríamos una excepción ClassNotFoundException, ya que la clase es com.mipaquete1.ClaseA y no mipaquete1.ClaseA.
Si nos posicionamos dentro de com y ejecutamos “java mipaquete1.ClaseA arg1 arg2, obtendríamos una excepción ClassNotFoundException por las mismas razones. La clase es com.mipaquete1.ClaseA y no hay más.
Veamos un ejemplo más: el siguiente código fuente recibe un número e imprime por pantalla el doble del número. Observar que hay que convertir de String (siempre recibiremos un String) al tipo de datos que queramos, en este caso un número entero.
package com.mipaquete1;
public class ClaseA {
public static void main(String[] args) {
int numero = Integer.parseInt(args[0]);
System.out.println("Doble del número recibido: " +
2*numero);
}
}
Classpath en JAVA
Supongamos que estamos en la carpeta siguiente y vamos a ejecutar ClaseA, que pertenece a com.mipaquete1 y que se encuentra en:
/aplicaciones/usuarios/curso/ejemplo/com/mipaquete1/ClaseA.class.
Nos posicionamos en /aplicaciones/usuarios/curso/ejemplo y ejecutamos:
aplicaciones/usuarios/curso/ejemplo> java com.mipaquete1.ClaseA
Para tener la flexibilidad de estar en otra ruta y ejecutar ClaseA, tendríamos que indicar un classpath. Por ejemplo, estando en otra carpeta:
>cd aplicaciones/usuarios/otraCarpeta
aplicaciones/usuarios/otraCarpeta>
java –classpath aplicaciones/usuarios/curso/ejemplo com.mipaquete1.ClaseA
También es posible tener una variable de entorno llamada CLASSPATH y así no tener que especificar la opción –classpath en cada ejecución.
En el classpath también podemos especificar más de una carpeta, así como ficheros jars, separados todos entre sí por un separador (; o : según sea Windows o Unix respectivamente).
Pasos de argumentos en JAVA
En Java existen tipos de datos primitivos y objetos. Cuando declaramos una variable en Java, ya sea una propiedad de una clase, o una variable interna a un método, o un argumento de entrada de un método, ésta puede ser o bien de tipo primitivo o bien una referencia a un objeto.
Como ya sabes, existen 8 tipos primitivos en el lenguaje. Las variables de tipo primitivo se alojan tal cual en memoria (en el stack).
Nombre |
Tamaño |
Rango de valores (inclusive |
byte |
8 bits |
-128 a 127 |
short |
16 bits |
-32789 a 32767 |
int |
32 bits |
-214748348 to 214748347 |
long |
64 bits |
-9223372036854775808 to 9223372036854775807 |
float |
32 bits |
2-149 to (2 - 2-23) · 2127 |
double |
64 bits |
2-1274 to (2 - 2-52) · 21023 |
char |
16 bits |
'\u0000' to '\uffff' (0 to 65535) |
boolean |
unspecified |
true or false |
Referencia a Objetos
Cuando una variable no es de tipo primitivo, entonces es una referencia a un objeto.
Estas se declaran utilizando variables de tipo una clase/interface o bien un array. Se trata de referencias que apuntan a un objeto determinado, y en realidad su valor es la dirección de memoria donde se localiza el objeto.
Cliente c = new Cliente(“Juan”);
La variable c contiene como valor la dirección de memoria donde está ubicada la instancia generada al hacer new Cliente().
Cuando se crea un objeto (el caso de new Cliente(“Juan”) ) se crea el objeto en memoria, en un espacio reservado para tal fin denominado heap.
Por su parte, el valor de c en realidad es numérico, contiene la ubicación del objeto creado en el heap.
La instancia generada al hacer new Cliente(“Juan” ) no tiene nombre y sólo puede ser accedida desde referencias que apunten a ella: c1 = c;
Según el código anterior, ahora puede accederse a la instancia de Cliente desde las referencias c y c1.
Si c y c1 son las únicas referencias a este objeto Cliente, e hiciésemos c = null; o c1 = null; entonces ya no sería posible acceder nunca más al objeto new Cliente(“Juan”) , que se ha quedado huérfano de referencias.
Para crear un objeto String podemos hacerlo sin la palabra reservada new, de la siguiente manera:
String s = “Hola Mundo”;
En este caso estamos hablando de literales.
En Java, los literales reciben un tratamiento diferente al resto de objetos. En el ejemplo anterior, la JVM crea un objeto de tipo String que representa al literal Hola Mundo, y lo almacena en una zona de memoria dentro del heap llamada string pool.
En Java los objetos String son inmutables (no se pueden modificar), así que la JVM puede darse el lujo de almacenar el literal en el string pool una única vez y reutilizarlo (como se muestra en la figura anterior).
Las siguientes referencias s1 y s2 realmente apuntan al mismo String en el string pool:
String s1=”Hola Mundo”;
String s2=”Hola Mundo”;
Y la JVM puede reutilizar de esta manera literales, porque el Stringno va a cambiar su valor ya que son inmutables. De esta manera, se garantiza que por ejemplo, s2 no puede modificar al String al que referencia, y por ende no va a afectar a s1.
Si ahora hacemos lo siguiente:
s2 = “Hola 2”;
en el string pool ahora tendríamos dos literales “Hola Mundo”, referenciado por s1, y “Hola 2” referenciado por s2.
Si ahora hacemos:
s1 = “Hola 1”;
tendríamos tres literales en el string pool, “Hola Mundo” sin referencias (pronto veremos que será eliminado del heap en algún momento), “Hola 1” referenciado por s1, y “Hola 2” referenciado por s2.
Pasos de argumentos: por valor
Pasos de Argumentos Primitivos:
Una variable que es pasada como parámetro de un método se denomina argumento. En Java, sólo existe una manera de pasar argumentos a métodos: por valor.
Eso significa que una copia de la variable es pasada como argumento al método y no la variable original. En cuanto a los retornos de los métodos, ocurre lo mismo, lo que es retornado es una copia de la variable retornada.
public int sumar (int a, int b) {
a = -1;
return a+b;
}
Para invocar este método:
int x = 3;
int y = 8;
int resultado = sumar(x,y);
El valor de x es copiado en el argumento del método a, que ocupa una posición de memoria diferente de x. La modificación a=-1 no afecta a x.
Índice del curso de Java SE 6:
- Programación en Java SE 6 desde cero: Parte 6
- Programación en Java SE 6 desde cero: Parte 5
- Programación en Java SE 6 desde cero: Parte 4
- Programación en Java SE 6 desde cero: Parte 3
- Programación en Java SE 6 desde cero: Parte 2
- Programación en Java SE 6 desde cero
¡Si el curso de Java SE 6 gratuito te sirvió de ayuda, deja un comentario!