Paquetes (Packages) en Java

Claúsula package

Un package (paquete) es una agrupación de clases afines. Equivale al concepto de librería existente en otros lenguajes o sistemas. Una clase puede definirse como perteneciente a un package y puede usar otras clases definidas en ese o en otros packages.

Los packages delimitan el espacio de nombres (space name). El nombre de una clase debe ser único dentro del package donde se define. Dos clases con el mismo nombre en dos packages distintos pueden coexistir e incluso pueden ser usadas en el mismo programa.

Una clase se declara perteneciente a un package con la clausula package, cuya sintaxis es:
package nombre_package;
La clausula package debe ser la primera sentencia del archivo fuente. Cualquier clase declarada en ese archivo pertenece al package indicado.
package miPackage;
. . .
class miClase {
. . .
}
declara que la clase miClase pertenece al package miPackage.

La claúsula package es opcional. Si no se utiliza, las clases declaradas en el archivo fuente no pertenecen a ningún package concreto, sino que pertenecen a un package por defecto sin nombre.

La agrupación de clases en packages es conveniente desde el punto de vista organizativo, para mantener bajo una ubicación común clases relacionadas que cooperan desde algún punto de vista. También resulta importante por la implicación que los packages tienen en los modificadores de acceso, que se explican en un capítulo posterior. Por ejemplo, un archivo que contenga las sentencias:

Claúsula import

Cuando se referencia cualquier clase dentro de otra se asume, si no se indica otra cosa, que ésta otra está declarada en el mismo package. Por ejemplo:
package Geometria;
. . .
class Circulo {
    Punto centro;
    . . .
}
En esta declaración definimos la clase Circulo perteneciente al package Geometria. Esta clase usa la clase Punto. El compilador y la JVM asumen que Punto pertenece también al package Geometria, y tal como está hecha la definición, para que la clase Punto sea accesible (conocida) por el compilador, es necesario que esté definida en el mismo package.

Si esto no es así, es necesario hacer accesible el espacio de nombres donde está definida la clase Punto a nuestra nueva clase. Esto se hace con la clausula import. Supongamos que la clase Punto estuviera definida de esta forma:
package GeometriaBase;

class Punto {
    int x , y;
}
Entonces, para usar la clase Punto en nuestra clase Circulo deberiamos poner:
package GeometriaAmpliada;

import GeometriaBase.*;

class Circulo {
    Punto centro;
    . . .
}
Con la claúsula import GeometriaBase.*; se hacen accesibles todos los nombres (todas las clases) declaradas en el package GeometriaBase. Si sólo se quisiera tener accesible la clase Punto se podría declarar: import GeometriaBase.Punto;

También es posible hacer accesibles los nombres de un package sin usar la clausula import calificando completamente los nombres de aquellas clases pertenecientes a otros packages. Por ejemplo:
package GeometriaAmpliada;

class Circulo {
    GeometriaBase.Punto centro;
    . . .
}
Sin embargo, si no se usa import es necesario especificar el nombre del package cada vez que se usa el nombre Punto.

La claúsula import simplemente indica al compilador donde debe buscar clases adicionales, cuando no pueda encontrarlas en el package actual y delimita los espacios de nombres y modificadores de acceso. Sin embargo, no tiene la implicación de 'importar' o copiar código fuente u objeto alguno. En una clase puede haber tantas sentencias import como sean necesarias. Las cláusulas import se colocan después de la cláusula package (si es que existe) y antes de las definiciones de las clases.

Nombres de los packages

Los packages se pueden nombrar usando nombres compuestos separados por puntos, de forma similar a como se componen las direcciones URL de Internet. Por ejemplo se puede tener un package de nombre misPackages.Geometria.Base. Cuando se utiliza esta estructura se habla de packages y subpackages. En el ejemplo misPackages es el Package base, Geometria es un subpackage de misPackages y Base es un subpackage de Geometria.

De esta forma se pueden tener los packages ordenados según una jerarquía equivalente a un sistema de archivos jerárquico.

El API de java está estructurado de esta forma, con un primer calificador (java o javax) que indica la base, un segundo calificador (awt, util, swing, etc.) que indica el grupo funcional de clases y opcionalmente subpackages en un tercer nivel, dependiendo de la amplitud del grupo. Cuando se crean packages de usuario no es recomendable usar nombres de packages que empiecen por java o javax.

Ubicación de packages en el sistema de archivos

Además del significado lógico descrito hasta ahora, los packages también tienen un significado físico que sirve para almacenar los módulos ejecutables (ficheros con extensión .class) en el sistema de archivos del ordenador.

Supongamos que definimos una clase de nombre miClase que pertenece a un package de nombre misPackages.Geometria.Base. Cuando la JVM vaya a cargar en memoria miClase buscará el módulo ejecutable (de nombre miClase.class) en un directorio en la ruta de acceso misPackages/Geometria/Base. Está ruta deberá existir y estar accesible a la JVM para que encuentre las clases. En el capítulo siguiente se dan detalles sobre compilación y ejecución de programas usando el compilador y la máquina virtural distribuida por SUN Microsystems (JDK).

Si una clase no pertenece a ningún package (no existe clausula package) se asume que pertenece a un package por defecto sin nombre, y la JVM buscará el archivo .class en el directorio actual.

Para que una clase pueda ser usada fuera del package donde se definió debe ser declarada con el modificador de acceso public, de la siguiente forma:
package GeometriaBase;

public class Punto {
    int x , y;
}
Nota: Los modificadores de acceso se explicarán detalladamente en un capítulo posterior.

Si una clase no se declara public sólo puede ser usada por clases que pertenezcan al mismo package.

REFERENCIAS:       arrakis

Strings en Java

La clase String

En Java no existe un tipo de datos primitivo que sirva para la manipulación de cadenas de caracteres. En su lugar se utiliza una clase definida en la API que es la clase String. Esto significa que en Java las cadenas de caracteres son, a todos los efectos, objetos que se manipulan como tales, aunque existen ciertas operaciones, como la creación de Strings, para los que el lenguaje tiene soporte directo, con lo que se simplifican algunas operaciones.

La clase String forma parte del package java.lang y se describe completamente en la documentación del API del JDK.cumentación del API del JDK.

Creación de Strings

Un String puede crearse como se crea cualquier otro objeto de cualquier clase; mediante el operador new:
String s = new String("Esto es una cadena de caracteres");
Observese que los literales de cadena de caracteres se indican entre comillas dobles ("), a diferencia de los caracteres, que utilizan comillas simples (').

Sin embargo, también es posible crear un String directamente, sin usar el operador new, haciendo una asignación simple (como si se tratara de un dato primitivo):
String s = "Esto es una cadena de caracteres";
Ambas expresiones conducen al mismo objeto.

Los Strings no se modifican una vez que se les ha asignado valor. Si se produce una reasignación se crea un nuevo objeto String con el nuevo contenido.

Además la clase String proporciona constructores para crear Strings a partir de arrays de caracteres y arrays de bytes. Consultar la documentación del API del JDK para más detalles.

Concatenación de Strings

Java define el operador + (suma) con un significado especial cuando las operandos son de tipo String. En este caso el operador suma significa concatenación. El resultado de la concatenación es un nuevo String compuesto por las dos cadenas, una tras otra. Por ejemplo:
String x = "Concatenar" + "Cadenas";
da como resultado el String "ConcatenarCadenas".

También es posible concatenar a un String datos primitivos, tanto numéricos como booleanos y char. Por ejemplo, se puede usar:
int i = 5;
String x = "El valor de i es " + i;
Cuando se usa el operador + y una de las variables de la expresión es un String, Java transforma la otra variable (si es de tipo primitivo) en un String y las concatena. Si la otra variable es una referencia a un objeto entonces invoca el método toString() que existe en todas las clases (es un método de la clase Object).

Otros métodos de la clase String

Método Descripción
char charAt(int index)
Devuelve el carácter en la posición indicada por index. El rango de index va de 0 a length() - 1.
boolean equals(Object obj)
Compara el String con el objeto especificado. El resultado es true si y solo si el argumento es no nulo y es un objeto String que contiene la misma secuencia de caracteres.
boolean equalsIgnoreCase(String s)
Compara el String con otro, ignorando consideraciones de mayúsculas y minúsculas. Los dos Strings se consideran iguales si tienen la misma longitud y, los caracteres correspondientes en ambos Strings son iguales sin tener en cuenta mayúsculas y minúsculas.
int indexOf(char c)
Devuelve el indice donde se produce la primera aparición de c. Devuelve -1 si c no está en el string.
int indexOf(String s)
Igual que el anterior pero buscando la subcadena representada por s.
int length()
Devuelve la longitud del String (número de caracteres).
String substring(int begin, int end)
Devuelve un substring desde el índice begin hasta el end.
static String valueOf(int i)
Devuelve un string que es la representación del entero i. Observese que este método es estático. Hay métodos equivalentes donde el argumento es un float, double, etc.
char[] toCharArray()
String toLowerCase()
String toUpperCase()
Transforman el string en un array de caracteres, o a mayúsculas o a minúsculas.


La clase StringBuffer

Dado que la clase String sólo manipula cadenas de caracteres constantes resulta poco conveniente cuando se precisa manipular intensivamente cadenas (reemplazadno caracteres, añadiendo o suprimiendo, etc.). Cuando esto es necesario puede usarse la clase StringBuffer definida también en el package java.lang. del API. Esta clase implanta un buffer dinámico y tiene métodos que permiten su manipulación comodamente. Ver la documentación del API.

REFERENCIAS:       arrakis

Arreglos en Java

Declaración y acceso

Un arreglo (array) es una colección ordenada de elementos del mismo tipo, que son accesibles a través de un índice.
Un arreglo puede contener datos primitivos o referencias a objetos.
Los arrays se deben declarar:
[modificadores] tipo_variable[ ] nombre;
Por ejemplo:
int [ ] a;
Punto [ ] p;
La declaración dice que a es un array de enteros y p un array de objetos de tipo Punto. De manera más precisa a es una referencia a una colección de enteros, aunque todavía no se sabe cuantos elementos tiene el array. p es una referencia a una colección de referencias que apuntarán objetos Punto.

Un array se crea como si se tratara de un objeto (de hecho las variables de tipo array son referencias):
a = new int [5];
p = new Punto[3];
En el momento de la creación del arreglo se dimensiona el mismo y se reserva la memoria necesaria.

También puede crearse de forma explícita asignando valores a todos los elementos del array en el momento de la declaración, de la siguiente forma:
int [ ] a = { 2 , 3 , 5 };
El acceso a los elementos del array se realiza indicando entre corchetes el elemento del array que se desea, teniendo en cuenta que siempre el primer elemento del array es el índice 0. Por ejemplo a[1]. En este ejemplo los índices del array de tres elementos son 0, 1 y 2. Si se intenta usar un índice que está fuera del rango válido para ese arreglo se produce un error (en realidad una excepción. Las excepciones serán tratadas en un capítulo posterior) de 'Indice fuera de rango'. En el ejemplo anterior se produce esta excepción si el índice es menor que 0 o mayor que 2.

Se puede conocer el número de elementos de un array usando la variable length. En el ejemplo a.length contiene el valor 3.

Un arreglo, como cualquier otra referencia puede formar parte de la lista de parámetros o constituir el valor de retorno de un método. En ambos casos se indica que se trata de un array con los corchetes que siguen al tipo. Por ejemplo:
String [ ] metodoConArrays ( Punto [ ] ) { . . }
El método metodoConArrays recibe como parámetro un array de Puntos y devuelve un array de Strings. El método podría invocarse de la siguiente forma:
Punto p [ ] = new Punto [3];
. . .
String [ ] resultado = metodoConArrays(p);

Arreglos multidimensionales

Es posible declarar arrays de más de una dimensión. Los conceptos son los mismos que para los arreglos monodimensionales.

Por ejemplo:
int [ ][ ] a = { { 1 , 2 } , { 3 , 4 } , { 5 , 6 } };
int x = a [1][0];   // contiene 3
int y = a [2][1];   // contiene 6
Se pueden recorrer los elementos de un array multidimensional, de la siguiente forma:
int [ ][ ] a = new int  [3][2];
for ( int i = 0 ; i < a.length ; i++ ) {
    for ( int j = 0 ; j < a[i].length ; j++) {
        a[i][j] = i * j;
    }
}
Obsérvese en el ejemplo la forma de acceder al tamaño de cada dimensión del array.

REFERENCIAS:       arrakis

Control de la ejecución

Resumen de operadores

La siguiente tabla muestra un resumen de los operadores clasificados por grupos:

Grupo de operador Operador Significado
Aritméticos +
-
*
/
%
Suma
Resta
Multiplicación
División
Resto
Relacionales >
>=
<
<=
==
!=
Mayor
Mayor o igual
Menor
Menor igual
Igual
Distinto
Lógicos &&
||
!
AND
OR
NOT
A nivel de bits &
|
^
<<
>>
>>>
AND
OR
NOT
Desplazamiento a la izquierda
Desplazamiento a la derecha rellenando con 1
Desplazamiento a la derecha rellenando con 0
Otros +
++
--
=
+=
-=
*=
/=
?:
Concatenación de cadenas
Autoincremento (actua como prefijo o sufijo)
Autodecremento (actua como prefijo o sufijo)
Asignación
Suma y asignación
Resta y asignación
Multiplicación y asignación
División y asignación
Condicional

Ejecución condicional

El formato general es:
if (expresion_booleana)
    sentencia
[else
    sentencia]
sentencia (a todo lo largo de este capítulo) puede ser una sola sentencia o un bloque de sentencias separadas por ; y enmarcadas por llaves { y }. Es decir
if (expresion_booleana) {
    sentencia;
    sentencia;
    . . .
}
else {
    sentencia;
    sentencia;
    . . .
}
expresion_booleana es una expresión que se evalua como true o false (es decir, de tipo booleano). Si el resultado es true la ejecución bifurca a la sentencia que sigue al if . En caso contrario se bifurca a la sentencia que sigue al else.

Los corchetes en el formato anterior indican que la clausula else es opcional.

Iteraciones con while

Sintaxis formato 1:
while (expresion_booleana)
    sentencia
Sintaxis formato 2:
do
    sentencia
while (expresion_booleana)
La sentencia o bloque se sentencias (se aplica la misma idea que para el if-else) se ejecuta mientras que la expresion_booleana se evalue como true.

La diferencia entre ambos formatos es que en el primero la expresión se evalua al principio del bloque de sentencias y en el segundo se evalua al final.

Iteraciones con for

El formato es:
for ( inicializacion ; expresion_booleana ; step )
    sentencia
inicializacion es una sentencia que se ejecuta la primera vez que se entra en el bucle for. Normalmente es una asignación, es opcional.

expresion_booleana es una expresión que se evalua antes de la ejecución de la sentencia, o bloque de sentencias, para cada iteración. La sentencia o bloque de sentencias se ejecutan mientras que la expresion_booleana se evalue como cierta. Es opcional.

step es una sentencia que se ejecuta cada vez que se llega al final de la sentencia o bloque de sentencias. Es opcional.

Una utilización clásica de un bucle de tipo for se muestra a continuación para evaluar un contador un número fijo de veces:
for ( int i = 1 ; i <= 10 ; i++ )
    sentencia
La sentencia (o bloque de sentencias) se evaluará 10 veces. En cada ejecución (pasada) el valor de la variable i irá variando desde 1 hasta 10 (inclusive). Cuando salga del bloque de sentencias i estará fuera de su ámbito (porque se define en el bloque for).

Si se omiten las tres clausulas del bucle se obtiene un bucle infinito:
for ( ; ; )
    sentencia
Obsérvese que se pueden omitir las clausulas pero no los separadores (;).

Evaluación múltiple

El formato es:
switch ( expresion_entera ) {
    case valor_entero:
        sentencia;
    break;
    case valor_entero:
        sentencia;
    break;
    . . .
    default:
    sentencia;
}
Cuidado: en el switch la expresión que se evalua no es una expresión booleana como en el if-else, sino una expresión entera.

Se ejecuta el bloque case cuyo valor coincida con el resultado de la expresión entera de la clausula switch. Se ejecuta hasta que se encuentra una sentencia break o se llega al final del switch.

Si ningún valor de case coincide con el resultado de la expresión entera se ejecuta el bloque default (si está presente).

default y break son opcionales.

Devolución de control

El formato es:
return valor
Se utiliza en los métodos para terminar la ejecución y devolver un valor a quien lo llamó.

valor debe ser del tipo declarado en el método.

valor es opcional. No debe existir cuando el método se declara de tipo void. En este caso, la claúsula return al final del método es opcional, pero puede usarse para devolver el control al llamador en cualquier momento.

Expresiones

La mayor parte del trabajo en un programa se hace mediante la evaluación de expresiones, bien por sus efectos tales como asignaciones a variables, bien por sus valores, que pueden ser usados como argumentos u operandos en expresiones mayores, o afectar a la secuencia de ejecución de instrucciones.

Cuando se evalua una expresión en un programa el resultado puede denotar una de tres cosas:
  • Una variable. (Si por ejemplo es una asignación)
  • Un valor. (Por ejemplo una expresión aritmética, booleana, una llamada a un método, etc.)
  • Nada. (Por ejemplo una llamada a un método declarado void)
Si la expresión denota una variable o un valor, entonces la expresión tiene siempre un tipo conocido en el momento de la compilación. Las reglas para determinar el tipo de la expresión varían dependiendo de la forma de las expresiones pero resultan bastante naturales. Por ejemplo, en una expresión aritmética con operandos de diversas precisiones el resultado es de un tipo tal que no se produzca pérdida de información, realizandose internamente las conversiones necesarias. El análisis pormenorizado de las conversiones de tipos, evaluaciones de expresiones, etc, queda fuera del ámbito de estos apuntes. En general puede decirse que es bastante similar a otros lenguajes, en particular C, teniendo en cuenta la característica primordial de Java de tratarse de un lenguaje con control fuerte de tipos.

REFERENCIAS:       arrakis