martes, 14 de agosto de 2007

Tutorial De Java

Objetos

Antes de entrar en el estudio de las Clases en Java, hay que presentar a los objetos, que son las instanciaciones de una clase. En este caso, haciendo un símil con la vida real, estamos colocando el carro delante del caballo, pero se supone que el lector tiene el bagaje suficiente como para entender lo que se expondrá, sin necesidad de conocer en profundidad lo que es y cómo funciona una clase. Además, esto permitirá obviar muchas cosas cuando se entre en el estudio de las clases. Y, en última instancia, siempre puede el lector pasar a la sección siguiente y luego volver aquí.

Un objeto es la instanciación de una clase y tiene un estado y un funcionamiento. El estado está contenido en sus variables (variables miembro), y el funcionamiento viene determinado por sus métodos. Las variables miembro pueden ser variables de instancia o variables de clase. Se activa el funcionamiento del objeto invocando a uno de sus métodos, que realizará una acción o modificará su estado, o ambas cosas. Cuando un objeto ya no se usa en C++, se destruye y la memoria que ocupaba se libera y es devuelta al Sistema. Cuando un objeto ya no se usa en Java, simplemente se anula. Posteriormente, el reciclador de memoria (garbage collector) puede recuperar esa memoria para devolverla al Sistema.

Las afirmaciones anteriores son un compendio de lo que se puede esperar de un objeto. A continuación se verá más en detalle cómo es la creación, el uso y la destrucción de un objeto. Se utilizará C++ como punto de comparación para dar el salto a Java, en deferencia a los muchos lectores que conocen los entresijos de los objetos en C++, de ahí que también que se inicie el estudio de la vida de los objetos suponiendo que se dispone de acceso a clases ya existentes o que se tiene conocimiento de cómo crearlas, aunque posteriormente se vuelva sobre ello.

Clases

Las clases son lo más simple de Java. Todo en Java forma parte de una clase, es una clase o describe como funciona una clase. El conocimiento de las clases es fundamental para poder entender los programas Java.Todas las acciones de los programas Java se colocan dentro del bloque de una clase o un objeto. Un objeto es una instancia de una clase. Todos los métodos se definen dentro del bloque de la clase, Java no soporta funciones o variables globales. Esto puede despistar a los programadores de C++, que pueden definir métodos fuera del bloque de la clase, pero esta posibilidad es más un intento de no separarse mucho y ser compatible con C, que un buen diseño orientado a objetos. Así pues, el esqueleto de cualquier aplicación Java se basa en la definición de una clase.

Todos los datos básicos, como los enteros, se deben declarar en las clases antes de hacer uso de ellos. En C la unidad fundamental son los ficheros con código fuente, en Java son las clases. De hecho son pocas las sentencias que se pueden colocar fuera del bloque de una clase. La palabra clave import (equivalente al #include) puede colocarse al principio de un fichero, fuera del bloque de la clase. Sin embargo, el compilador reemplazará esa sentencia con el contenido del fichero que se indique, que consistirá, como es de suponer, en más clases.
La definición de una clase consta de dos partes, la declaración y el cuerpo, según la siguiente sintaxis:DeclaracionClase {
CuerpoClase
}
sin el punto y coma (;) final característico de C++ para señalar el final de la definición de una clase.

La declaración de la clase indica al compilador el nombre de la clase, la clase de la que deriva (su superclase), los privilegios de acceso a la clase (pública, abstracta, final) y si la clase implementa o no, uno o varios interfaces. El nombre de la clase debe ser un identificador válido en Java. Por convención, muchos programadores Java empiezan el nombre de las clase Java con una letra mayúscula.

Cada clase Java deriva, directa o indirectamente, de la clase Object. La clase padre inmediatamente superior a la clase que se está declarando se conoce como superclass. Si no se especifica la superclase de la que deriva una clase, se entiende que deriva directamente de la clase Object (definida en el paquete java.lang).

En la declaración de una clase se utiliza la palabra clave extends para especificar la superclase, de la forma:class MiClase extends SuperClase {
// cuerpo de la clase
}
Los programadores C++ observarán que no se indica nada sobre los privilegios de acceso a la clase, es decir, si es pública, privada o protegida. O sea, la herencia de los métodos de acceso a una clase no se pueden utilizar en Java para modificar el control de acceso asignado a un miembro de la clase padre.

Al igual que en C++, una clase hereda las variables y métodos de su superclase y de la superclase de esa clase, etc.; es decir, de todo el árbol de jerarquía de clases desde la clase que estamos declarando hasta la raíz del árbol: Object. En otras palabras, un objeto que es instanciado desde una clase determinada, contiene todas las variables y métodos de instancia definidos para esta clase y sus antecesores; aunque los métodos pueden ser modificados (sobreescritos) en algún lugar.

Una clase puede implementar uno o más interfaces, declarándose esto utilizando la palabra clave implements, seguida de la lista de interfaces que implementa, se paradas por coma (,), de la forma:class MiClase extends SuperClase implements MiInterfaz,TuInterfaz {
// cuerpo de la clase
}
Cuando una clase indique que implementa un interfaz, se puede asegurar que proporciona una definición para todos y cada uno de los métodos declarados en ese interfaz; en caso contrario, el compilador generará errores al no poder resolver los métodos del interfaz en la clase que lo implementa.

Hay cierta similitud entre un interfaz y una clase abstracta, aunque las definiciones de métodos no están permitidas en un interfaz y sí se pueden definir en una clase abstracta. El propósito de los interfaces es proporcionar nombres, es decir, solamente declara lo que necesita implementar el interfaz, pero no cómo se ha de realizar esa implementación; es una forma de encapsulación de los protocolos de los métodos sin forzar al usuario a utilizar la herencia.

Cuando se implementa un interfaz, los nombres de los métodos de la clase deben coincidir con los nombres de los métodos que están declarados en es interfaz, todos y cada uno de ellos.
El cuerpo de la clase contiene las declaraciones, y posiblemente la inicialización, de todos los datos miembros, tanto variables de clase como variables de instancia, y la definición completa de todos los métodos.

Las variables pueden declararse dentro del cuerpo de la clase o dentro del cuerpo de un método de la clase. Sin embargo, éstas últimas no son variables miembro de la clase, sino variables locales del método en la que están declaradas.

Un objeto instanciado de una clase tiene un estado definido por el valor actual de las variables de instancia y un entorno definido por los métodos de instancia. Es típico de la programación orientada a objetos el restringir el acceso a las variables y proporcionar métodos que permitan el acceso a esas variables, aunque esto no es estrictamente necesario.

Alguna o todas las variables pueden declararse para que se comporten como si fuesen constantes, utilizando la palabra clave final.

Los objetos instanciados desde una clase contienen todos los métodos de instancia de esa clase y también todos los métodos heredados de la superclase y sus antecesores. Por ejemplo, todos los objetos en Java heredan, directa o indirectamente, de la clase Object; luego todo objeto contiene los miembros de la clase Object, es decir, la clase Object define el esta y entorno básicos para todo objeto Java. Esto difiere significativamente de C++, en donde no hay una clase central de la que se derive todo, por lo que no hay una definición de un estado básico en C++.

Las características más importantes que la clase Object cede a sus descendientes son:
Posibilidad de cooperación consigo o con otro objeto
Posibilidad de conversión automática a String
Posibilidad de espera sobre una variable de condición
Posibilidad de notificación a otros objetos del estado de la variable de condición
Hay muchas otras características que se irán viendo a lo largo de los ejemplos y secciones del Tutorial.

La sintaxis general de definición de clase se podría extender tal como se muestra en el siguiente diagrama, que debe tomarse solamente como referencia y no al pie de la letra:NombreDeLaClase {
// declaración de las variables de instancia
// declaración de las variables de la clase
metodoDeInstancia() {
// variables locales
// código
}
metodoDeLaClase() {
// variables locales
// código
}
}
Tipos de Clases

Hasta ahora sólo se ha utilizado la palabra clave public para calificar el nombre de las clases que hemos visto, pero hay tres modificadores más. Los tipos de clases que podemos definir son:
public

Las clases public son accesibles desde otras clases, bien sea directamente o por herencia, desde clases declaradas fuera del paquete que contiene a esas clases públicas, ya que, por defecto, las clases solamente son accesibles por otras clases declaradas dentro del mismo paquete en el que se han declarado. Para acceder desde otros paquetes, primero tienen que ser importadas. La sintaxis es:public class miClase extends SuperClase implements miInterface,TuInterface {
// cuerpo de la clase
}
Aquí la palabra clave public se utiliza en un contexto diferente del que se emplea cuando se define internamente la clase, junto con private y protected.
abstract

Una clase abstract tiene al menos un método abstracto. Una clase abstracta no se instancia, sino que se utiliza como clase base para la herencia. Es el equivalente al prototipo de una función en C++.
final

Una clase final se declara como la clase que termina una cadena de herencia, es lo contrario a una clase abstracta. Nadie puede heredar de una clase final. Por ejemplo, la clase Math es una clase final. Aunque es técnicamente posible declarar clases con varias combinaciones de public, abstract y final, la declaración de una clase abstracta y a la vez final no tiene sentido, y el compilador no permitirá que se declare una clase con esos dos modificadores juntos.
synchronizable

Este modificador especifica que todos los métodos definidos en la clase son sincronizados, es decir, que no se puede acceder al mismo tiempo a ellos desde distintos threads; el sistema se encarga de colocar los flags necesarios para evitarlo. Este mecanismo hace que desde threads diferentes se puedan modificar las mismas variables sin que haya problemas de que se sobreescriban.

Si no se utiliza alguno de los modificadores expuestos, por defecto, Java asume que una clase es:
No final
No abstracta
Subclase de la clase Object
No implementa interfaz alguno
Variables Miembro

Una clase en Java puede contener variables y métodos. Las variables pueden ser tipos primitivos como int, char, etc. Los métodos son funciones.
Por ejemplo, en el siguiente trozo de código podemos observarlo: public class MiClase {
int i;
public MiClase() {
i = 10;
}
public void Suma_a_i( int j ) {
int suma;
suma = i + j;
}
}
La clase MiClase contiene una variable (i) y dos métodos, MiClase() que es el constructor de la clase y Suma_a_i( int j ).

La declaración de una variable miembro aparece dentro del cuerpo de la clase, pero fuera del cuerpo de cualquier método de esa clase. Si se declara dentro de un métodos, será una variable local del método y no una variable miembro de la clase. En el ejemplo anterior, i es una variable miembro de la clase y suma es una variable local del método Suma_ a_i().
El tipo de una variable determina los valores que se le pueden asignar y las operaciones que se pueden realizar con ella.

El nombre de una variable ha de ser un identificador válido en Java. Por convenio, los programadores Java empiezan los nombres de variables con una letra minúscula, pero no es imprescindible. Los nombres de las variables han de ser únicos dentro de la clase y se permite que haya variables y métodos con el mismo nombre, a diferencia de C++, en donde se produce un error de compilación si se da este caso.

Para los programadores C++, se pueden señalar algunas diferencias entre Java y C++ respecto a la sintaxis e interpretación en la declaración de variables miembro de una clase, como son:
En los dos lenguajes se permite la declaración de variables miembro estáticas con una sintaxis parecida, aunque en C++, se deben redeclarar estas variables fuera de la definición de la clase utilizando el nombre de la clase, el nombre de la variable y el operador de ámbito

En los dos lenguajes se permite la utilización de modificadores de acceso a las variables miembro utilizando las palabras clave public, private y protected, aunque la sintaxis e interpretación son diferentes en los dos lenguajes

Los dos lenguajes reconocen la palabra clave volatile, aunque Java la ignora
C++ no reconoce las palabras clave transient y final, utilizadas en la declaración de variables miembro en Java

Aparte de las diferencias de sintaxis anteriores, Java permite la inicialización de variables miembro de tipos primitivos en el momento de la declaración y esto no es posible en C++.
La sintaxis completa de la declaración de una variable miembro de una clase en Java sería:[especificador_de_acceso][static][final][transient][volatile]
tipo nombreVariable [= valor_inicial];
Ambito de una Variable

Los bloques de sentencias compuestas en Java se delimitan con dos llaves. Las variables de Java sólo son válidas desde el punto donde están declaradas hasta el final de la sentencia compuesta que la engloba. Se pueden anidar estas sentencias compuestas, y cada una puede contener su propio conjunto de declaraciones de variables locales. Sin embargo, no se puede declarar una variable con el mismo nombre que una de ámbito exterior.

El siguiente ejemplo intenta declarar dos variables separadas con el mismo nombre. En C y C++ son distintas, porque están declaradas dentro de ámbitos diferentes. En Java, esto es ilegal. class Ambito {
int i = 1; // ámbito exterior
{ // crea un nuevo ámbito
int i = 2; // error de compilación
}
}
Variables de Instancia
La declaración de una variable miembro dentro de la definición de una clase sin anteponerle la palabra clave static, hace que sea una variable de instancia en todos los objetos de la clase. El significado de variable de instancia sería, más o menos, que cualquier objeto instanciado de esa clase contiene su propia copia de toda variable de instancia. Si se examinara la zona de memoria reservada a cada objeto de la clase, se encontraría la reserva realizada para todas las variables de instancia de la clase. En otras palabras, como un objeto es una instancia de una clase, y como cada objeto tiene su propia copia de un dato miembro particular de la clase, entonces se puede denominar a ese dato miembro como variable de instancia.

En Java, se accede a las variables de instancia asociadas a un objeto determinado utilizando el nombre del objeto, el operador punto (.) y el nombre de la variable: miObjeto.miVariableDeInstancia;

En C++, se puede acceder a las variables de instancia de un objeto a través de la misma sintaxis, pero también utilizando el operador flecha (->), que utiliza como origen una variable puntero que apunta al objeto: miVariablePuntero->miVariableDeInstancia;
Variables Estáticas

La declaración de un dato miembro de una clase usando static, crea una variable de clase o variable estática de la clase. El significado de variable estática es que todas las instancias de la clase (todos los objetos instanciados de la clase) contienen la mismas variables de clase o estáticas. En otras palabras, en un momento determinado se puede querer crear una clase en la que el valor de una variable de instancia sea el mismo (y de hecho sea la misma variable) para todos los objetos instanciados a partir de esa clase. Es decir, que exista una única copia de la variable de instancia, entonces es cuando debe usarse la palabra clave static.class Documento extends Pagina {
static int version = 10;
}
El valor de la variable version será el mismo para cualquier objeto instanciado de la clase Documento. Siempre que un objeto instanciado de Documento cambie la variable version, ésta cambiará para todos los objetos.

En C++, es necesario redeclarar todas las variables estáticas fuera de la definición de la clase, produciendo una variable pseudo-global, es decir, una variable que es global en lo que a los objetos de esa clase se refiere.

Si se examinara en este caso la zona de memoria reservada por el sistema para cada objeto, se encontraría con que todos los objetos comparten la misma zona de memoria para cada una de las variables estáticas, por ello se llaman también variables de clase, porque son comunes a la clase, a todos los objetos instanciados de la clase.

Tanto en Java como en C++, se puede acceder a las variables de clase utilizando el nombre de la clase y el nombre de la variable, no es necesario instanciar ningún objeto de la clase para acceder a las variables de clase.

En C++, a las variables de clase se accede utilizando el operador de ámbito junto con el nombre de la clase y el nombre de la variable: miVariable::miVariableDeClase;
En Java, a las variables de clase se accede utilizando el nombre de la clase, el nombre de la variable y el operador punto (.). La siguiente línea de código, ya archivista, se utiliza para acceder a la variable out de la clase System. En el proceso, se accede al método println() de la variable de clase que presenta una cadena en el dispositivo estándar de salida. System.out.println( "Hola, Mundo" );
Es importante recordar que todos los objetos de la clase comparten las misma variables de clase, porque si alguno de ellos modifica alguna de esas variables de clase, quedarán modificadas para todos los objetos de la clase. Esto puede utilizarse como una forma de comunicación entre objetos.

Constantes
En Java, se utiliza la palabra clave final para indicar que una variable debe comportarse como si fuese constante, significando con esto que no se permite su modificación una vez que haya sido declarada e inicializada.
Como es una constante, se le ha de proporcionar un valor en el momento en que se declare, por ejemplo: class Elipse {
final float PI = 3.14159;
. . .
}
Si se intenta modificar el valor de una variable final desde el código de la aplicación, se generará un error de compilación.

Si se usa la palabra clave final con una variable o clase estática, se pueden crear constantes de clase, haciendo de esto modo un uso altamente eficiente de la memoria, porque no se necesitarían múltiples copias de las constantes.
La palabra clave final también se puede aplicar a métodos, significando en este caso que los métodos no pueden ser sobreescritos.

Métodos

Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras clases. La implementación de un método consta de dos partes, una declaración y un cuerpo. La declaración en Java de un método se puede expresar esquemáticamente como: tipoRetorno nombreMetodo( [lista_de_argumentos] ) {
cuerpoMetodo
}
En C++, el método puede declararse dentro de la definición de la clase, aunque también puede colocarse la definición completa del método fuera de la clase, convirtiéndose en una función inline. En Java, la definición completa del método debe estar dentro de la definición de la clase y no se permite la posibilidad de métodos inline, por lo tanto, Java no proporciona al programador distinciones entre métodos normales y métodos inline.

Los métodos pueden tener numerosos atributos a la hora de declararlos, incluyendo el control de acceso, si es estático o no estático, etc. La sintaxis utilizada para hacer que un método sea estático y su interpretación, es semejante en Java y en C++. Sin embargo, la sintaxis utilizada para establecer el control de acceso y su interpretación, es muy diferente en Java y en C++.
La lista de argumentos es opcional, tanto en Java como en C++, y en los dos casos puede limitarse a su mínima expresión consistente en dos paréntesis, sin parámetro alguno en su interior. Opcionalmente, C++ permite utilizar la palabra void para indicar que la lista de argumentos está vacía, en Java no se usa. Los parámetros, o argumentos, se utilizan para pasar información al cuerpo del método.

La sintaxis de la declaración completa de un método es la que se muestra a continuación con los items opcionales en itálica y los items requeridos en negrilla:especificadorAcceso static abstract
final native synchronized tipoRetorno nombreMetodo( lista_de_argumentos )
throws listaEscepciones

especificadorAcceso, determina si otros objetos pueden acceder al método y cómo pueden hacerlo. Está soportado en Java y en C++, pero la sintaxis e interpretación es considerablemente diferente.

static, indica que los métodos pueden ser accedidos sin necesidad de instanciar un objeto del tipo que determina la clase. C++ y Java son similares en el soporte de esta característica.
abstract, indica que el método no está definido en la clase, sino que se encuentra declarado ahí para ser definido en una subclase (sobreescrito). C++ también soporta esta capacidad con una sintaxis diferente a Java, pero con similar interpretación.
final, evita que un método pueda ser sobreescrito.

native, son métodos escritos es otro lenguaje. Java soporta actualmente C y C++.
synchronized, se usa en el soporte de multithreading, que se verá también en este Tutorial.
lista_de_argumentos, es la lista opcional de parámentros que se pueden pasar al método
throws listaExcepciones, indica las excepciones que puede generar y manipular el método. También se verán en este Tutorial a fondo las excepciones en Java.
Valor de Retorno de un Método

En Java es imprescindible que a la hora de la declaración de un método, se indique el tipo de dato que ha de devolver. Si no devuelve ningún valor, se indicará el tipo void como retorno.
Los métodos y funciones en C++ pueden devolver una variable u objeto, bien sea por valor (se devuelve una copia), por puntero o por referencia. Java no soporta punteros, así que no puede devolver nada por puntero. Todos los tipos primitivos en Java se devuelven por valor y todos los objetos se devuelven por referencia. El retorno de la referencia a un objeto en Java es similar a devolver un puntero a un objeto situado en memoria dinámica en C++, excepto que la sintaxis es mucho más simple en Java, en donde el item que se devuelve es la dirección de la posición en memoria dinámica donde se encuentra almacenado el objeto.

Para devolver un valor se utiliza la palabra clave return. La palabra clave return va seguida de una expresión que será evaluada para saber el valor de retorno. Esta expresión puede ser compleja o puede ser simplemente el nombre de un objeto, una variable de tipo primitivo o una constante.
El ejemplo
java506.java ilustra el retorno por valor y por referencia. // Un objeto de esta clase sera devuelto por referencia
class miClase {
int varInstancia = 10;
}
Si un programa Java devuelve una referencia a un objeto y esa referencia no es asignada a ninguna variable, o utilizada en una expresión, el objeto se marca inmediatamente para que el reciclador de memoria en su siguiente ejecución devuelve la memoria ocupada por el objeto al sistema, asumiendo que la dirección no se encuentra ya almacenada en ninguna otra variable. En C++, si un programa devuelve un puntero a un objeto situado en memoria dinámica y el valor de ese puntero no se asigna a una variable, la posibilidad de devolver la memoria al sistema se pierde y se producirá un memory leak, asumiendo que la dirección no está ya disponible para almacenar ninguna otra variable.

Tanto en Java como en C++ el tipo del valor de retorno debe coincidir con el tipo de retorno que se ha indicado en la declaración del método; aunque en Java, el tipo actual de retorno puede ser una subclase del tipo que se ha indicado en la declaración del método, lo cual no se permite en C++. En Java esto es posible porque todas las clases heredan desde un objeto raíz común a todos ellos: Object.

En general, se permite almacenar una referencia a un objeto en una variable de referencia que sea una superclase de ese objeto. También se puede utilizar un interfaz como tipo de retorno, en cuyo caso, el objeto retornado debe implementar dicho interfaz.
Nombre del Método

El nombre del método puede ser cualquier identificador legal en Java. Java soporta el concepto de sobrecarga de métodos, es decir, permite que dos métodos compartan el mismo nombre pero con diferente lista de argumentos, de forma que el compilador pueda diferenciar claramente cuando se invoca a uno o a otro, en función de los parámetros que se utilicen en la llamada al método.

El siguiente fragmento de código muestra una clase Java con cuatro métodos sobrecargados, el último no es legal porque tiene el mismo nombre y lista de argumentos que otro previamente declarado: class MiClase {
. . .
void miMetodo( int x,int y ) { . . . }
void miMetodo( int x ) { . . . }
void miMetodo( int x,float y ) { . . . }
// void miMetodo( int a,float b ) { . . . } // no válido
}
Todo lenguaje de programación orientado a objetos debe soportar las características de encapsulación, herencia y polimorfismo. La sobrecarga de métodos es considerada por algunos autores como polimorfismo en tiempo de compilación.

En C++, dos versiones sobrecargadas de una misma función pueden devolver tipos diferentes. En Java, los métodos sobrecargados siempre deben devolver el mismo tipo.
Métodos de Instancia

Cuando se incluye un método en una definición de una clase Java sin utilizar la palabra clave static, estamos generando un método de instancia. Aunque cada objeto de la clase no contiene su propia copia de un método de instancia (no existen múltiples copias del método en memoria), el resultado final es como si fuese así, como si cada objeto dispusiese de su propia copia del método.
Cuando se invoca un método de instancia a través de un objeto determinado, si este método referencia a variables de instancia de la clase, en realidad se están referenciando variables de instancia específicas del objeto específico que se está invocando.

La llamada a los métodos de instancia en Java se realiza utilizando el nombre del objeto, el operador punto y el nombre del método. miObjeto.miMetodoDeInstancia();
En C++, se puede acceder de este mismo modo o utilizando una variable puntero que apunte al objeto miPunteroAlObjeto->miMetodoDeInstancia();
Los métodos de instancia tienen acceso tanto a las variables de instancia como a las variables de clase, tanto en Java como en C++.
Métodos Estáticos

Cuando una función es incluida en una definición de clase C++, o un método e incluso en una definición de una clase Java, y se utiliza la palabra static, se obtiene un método estático o método de clase.

Lo más significativo de los métodos de clase es que pueden ser invocados sin necesidad de que haya que instanciar ningún objeto de la clase. En Java se puede invocar un método de clase utilizando el nombre de la clase, el operador punto y el nombre del método. MiClase.miMetodoDeClase();
En C++, hay que utilizar el operador de resolución de ámbito para poder invocar a un método de clase: MiClase::miMetodoDeClase();
En Java, los métodos de clase operan solamente como variables de clase; no tienen acceso a variables de instancia de la clase, a no ser que se cree un nuevo objeto y se acceda a las variables de instancia a través de ese objeto.

Si se observa el siguiente trozo de código de ejemplo: class Documento extends Pagina {
static int version = 10;
int numero_de_capitulos;
static void annade_un_capitulo() {
numero_de_capitulos++; // esto no funciona
}
static void modifica_version( int i ) {
version++; // esto si funciona
}
}
la modificación de la variable numero_de_capitulos no funciona porque se está violando una de las reglas de acceso al intentar acceder desde un método estático a una variable no estática.
Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma página de variables; es decir, todos los objetos que se generen comparten la misma zona de memoria. Los métodos estáticos se usan para acceder solamente a variables estáticas. class UnaClase {
int var;
UnaClase() {
var = 5;
}
unMetodo() {
var += 5;
}
}
En el código anterior, si se llama al método unMetodo() a través de un puntero a función, no se podría acceder a var, porque al utilizar un puntero a función no se pasa implícitamente el puntero al propio objeto (this). Sin embargo, sí se podría acceder a var si fuese estática, porque siempre estaría en la misma posición de memoria para todos los objetos que se creasen de la clase UnaClase.
Paso de parámetros

En C++, se puede declarar un método en una clase y definirlo luego dentro de la clase (bajo ciertas condiciones) o definirlo fuera de la clase. A la hora de declararlo, es necesario indicar el tipo de argumentos que necesita, pero no se requiere indicar sus nombres (aunque pueda hacerse). A la hora de definir el método sí tiene que indicarse el nombre de los argumentos que necesita el método.

En Java, todos los métodos deben estar declarados y definidos dentro de la clase, y hay que indicar el tipo y nombre de los argumentos o parámetros que acepta. Los argumentos son como variables locales declaradas en el cuerpo del método que están inicializadas al valor que se pasa como parámetro en la invocación del método.

En Java, todos los argumentos de tipos primitivos deben pasarse por valor, mientras que los objetos deben pasarse por referencia. Cuando se pasa un objeto por referencia, se está pasando la dirección de memoria en la que se encuentra almacenado el objeto.

Si se modifica una variable que haya sido pasada por valor, no se modificará la variable original que se haya utilizado para invocar al método, mientras que si se modifica una variable pasada por referencia, la variable original del método de llamada se verá afectada de los cambios que se produzcan en el método al que se le ha pasado como argumento.

El ejemplo java515.java se ilustra el paso de parámetros de tipo primitivo y también el paso de objetos, por valor y por referencia, respectivamente.// Esta clase se usa para instanciar un objeto referencia
class MiClase {
int varInstancia = 100;
}

// Clase principal
class java515 {

// Función para ilustrar el paso de parámetros
void pasoVariables( int varPrim,MiClase varRef ) {
System.out.println( "--> Entrada en la funcion pasoVariables" );
System.out.println( "Valor de la variable primitiva: "+varPrim );
System.out.println( "Valor contenido en el objeto: "+
varRef.varInstancia );

System.out.println( "-> Modificamos los valores" );
varRef.varInstancia = 101;
varPrim = 201;

System.out.println( "--> Todavia en la funcion pasoVariables" );
System.out.println( "Valor de la variable primitiva: "+varPrim );
System.out.println( "Valor contenido en el objeto: "+
varRef.varInstancia );
}

public static void main( String args[] ) {
// Instanciamos un objeto para acceder a sus métodos
java515 aObj = new java515();

// Instanciamos un objeto normal
MiClase obj = new MiClase();
// Instanciamos una variable de tipo primitivo
int varPrim = 200;

System.out.println( "> Estamos en main()" );
System.out.println( "Valor de la variable primitiva: "+varPrim );
System.out.println( "Valor contenido en el objeto: "+
obj.varInstancia );
// Llamamos al método del objeto
aObj.pasoVariables( varPrim,obj );

System.out.println( "> Volvemos a main()" );
System.out.println( "Valor de la variable primitiva, todavia : "+
varPrim );
System.out.println( "Valor contenido ahora en el objeto: "+
obj.varInstancia );
}
}
En C++, se puede pasar como parámetro un puntero que apunte a una función dentro de otra función, y utilizar este puntero en la segunda función para llamar a la primera. Esta capacidad no está directamente soportada en Java. Sin embargo, en algunos casos, se puede conseguir casi lo mismo encapsulando la primero función como un método de instancia de un objeto y luego pasar el objeto a otro método, donde el primer método se puede ejecutar a través del objeto.

Tanto en Java como en C++, los métodos tienen acceso directo a las variables miembro de la clase. El nombre de un argumento puede tener el mismo nombre que una variable miembro de la clase. En este caso, la variable local que resulta del argumento del método, oculta a la variable miembro de la clase.

Cuando se instancia un método se pasa siempre una referencia al propio objeto que ha llamado al método, es la referencia this.

Constructor
Tanto Java como C++ soportan la sobrecarga de métodos, es decir, que dos o más métodos puedan tener el mismo nombre, pero distinta lista de argumentos en su invocación. Si se sobrecarga un método, el compilador determinará ya en tiempo de compilación, en base a lista de argumentos con que se llame al método, cual es la versión del método que debe utilizar.

Tanto Java como C++ soportan la noción de constructor. El constructor es un tipo específico de método que siempre tiene el mismo nombre que la clase y se utiliza para construir objetos de esa clase. No tiene tipo de dato específico de retorno, ni siquiera void. Esto se debe a que el tipo específico que debe devolver un constructor de clase es el propio tipo de la clase.

En este caso, pues, no se puede especificar un tipo de retorno, ni se puede colocar ninguna sentencia que devuelva un valor. Los constructores pueden sobrecargarse, y aunque puedan contener código, su función primordial es inicializar el nuevo objeto que se instancia de la clase. En C++, el constructor se invoca automáticamente a la hora de crear un objeto. En Java, ha de hacerse una llamada explícita al constructor para instanciar un nuevo objeto.

Cuando se declara una clase en Java, se pueden declarar uno o más constructores opcionales que realizan la inicialización cuando se instancia (se crea una ocurrencia) un objeto de dicha clase.
Utilizando el código de la sección anterior, cuando se crea una nueva instancia de MiClase, se crean (instancias) todos los métodos y variables, y se llama al constructor de la clase: MiClase mc;
mc = new MiClase();
La palabra clave new se usa para crear una instancia de la clase. Antes de ser instanciada con new no consume memoria, simplemente es una declaración de tipo. Después de ser instanciado un nuevo objeto mc, el valor de i en el objeto mc será igual a 10. Se puede referenciar la variable (de instancia) i con el nombre del objeto: mc.i++; // incrementa la instancia de i de mc
Al tener mc todas las variables y métodos de MiClase, se puede usar la primera sintaxis para llamar al método Suma_a_i() utilizando el nuevo nombre de clase mc: mc.Suma_a_i( 10 );
y ahora la variable mc.i vale 21.

Luego, en Java, cuando se instancia un objeto, siempre se hace una llamada directa al constructor como argumento del operador new. Este operador se encarga de que el sistema proporcione memoria para contener al objeto que se va a crear.

En C++, los objetos pueden instanciarse de diferentes formas, pero en Java solamente se pueden instanciar en la pila de memoria, es decir, solamente se pueden instanciar utilizando el operador new para poder solicitar memoria al sistema en tiempo de ejecución y utilizar el constructor para instanciar el objeto en esa zona de memoria. Si al intentar instanciar un objeto, la Máquina Virtual Java (JVM) no puede localizar la memoria que necesita ese objeto, bien inmediatamente o haciendo ejecutarse al reciclador de memoria, el sistema lanzará un OutOfMemoryError.

Tanto en Java como en C++, si no se proporciona explícitamente un constructor, el sistema proporciona uno por defecto que inicializará automáticamente todas las variables miembro a cero o su equivalente, en Java. En C++, el constructor de defecto no realiza ningún tipo de inicialización.

Se puede pensar en el constructor de defecto en Java como un método que tiene el mismo nombre que la clase y una lista de argumentos vacía. Y en C++, el constructor de defecto sería como la llamada cuando se instancia un objeto sin parámetros MiClase objeto;
En ambos lenguajes, si se proporciona uno o más constructores, el constructor de defecto no se proporciona automáticamente y si fuese necesaria su utilización, tendría que proporcionarlo explícitamente el programa.

Las dos sentencias siguientes muestran cómo se utiliza el constructor en Java para declarar, instanciar y, opcionalmente, inicializar un objeto: MiClase miObjeto = new MiClase();
MiClase miObjeto = new MiClase( 1,2,3 );
Las dos sentencias devuelven una referencia al nuevo objeto que es almacenada en la variable miObjeto. También se puede invocar al constructor sin asignar la referencia a una variable. Esto es útil cuando un método requiere un objeto de un tipo determinado como argumento, ya que se puede incluir una llamada al constructor de este objeto en la llamada al método: miMetodo( new MiConstructor( 1,2,3 ) );
Aquí se instancia e inicializa un objeto y se pasa a la función. Para que el programa compile adecuadamente, debe existir una versión de la función que espere recibir un objeto de ese tipo como parámetro.

Tanto en Java como en C++, cuando un método o una función comienza su ejecución, todos los parámetros se crean como variables locales automáticas. En este caso, el objeto es instanciado en conjunción con la llamada a la función que será utilizada para inicializar esas variables locales cuando comience la ejecución y luego serán guardadas. Como son automáticas, cuando el método concluye su ejecución, se destruirá (en C++) o será marcado para su destrucción (en Java).
En el siguiente ejemplo,
java507.java, se ilustran algunos de los conceptos sobre constructores que se han planteado en esta sección.class MiClase {
int varInstancia;

// Este es el constructor parametrizado
MiClase( int dato ) {
// rellenamos la variable de instancia con los datos
// que se pasan al constructor
varInstancia = dato;
}

void verVarInstancia() {
System.out.println( "El Objeto contiene " + varInstancia );
}
}

class java507 {
public static void main( String args[] ) {
System.out.println( "Lanzando la aplicacion" );
// Instanciamos un objeto de este tipo llamando al
// constructor de defecto
java507 obj = new java507();
// Llamamos a la funcion pasandole un constructor
// parametrizado como parametro
obj.miFuncion( new MiClase( 100 ) );
}

// Esta funcion recibe un objeto y llama a uno de sus metodos
// para presentar en pantalla el dato que contiene el objeto
void miFuncion( MiClase objeto){
objeto.verVarInstancia();
}
}
Herencia

En casos en que se vea involucrada la herencia, los constructores toman un significado especial porque lo normal es que la subclase necesite que se ejecute el constructor de la superclase antes que su propio constructor, para que se inicialicen correctamente aquellas variables que deriven de la superclase. En C++ y Java, la sintaxis para conseguir esto es sencilla y consiste en incluir en el cuerpo del constructor de la subclase como primera línea de código la siguiente sentencia: super( parametros_opcionales );
Esto hará que se ejecute el constructor de la superclase, utilizando los parámetros que se pasen para la inicialización. En el código del ejemplo siguiente,
java508.java, se ilustra el uso de esta palabra clase para llamar al constructor de la superclase desde una subclase.class SuperClase {
int varInstancia;

// Es necesario proporcionar el constructor por defecto,que
// es aquel que no tiene parametros de llamada
SuperClase(){}

// Este es el constructor parametrizado de la superclase
SuperClase( int pDato ) {
System.out.println(
"Dentro del constructor de la SuperClase" );
varInstancia = pDato;
}

void verVarInstancia() {
System.out.println( "El Objeto contiene " + varInstancia );
}
}

class SubClase extends SuperClase {
// Este es el constructor parametrizado de la subclase
SubClase( int bDato ) {
// La siguiente sentencia println no compila, la llamada
// a super() debe estar al principio de un metodo en caso de
// que aparezca
// System.out.println( "En el constructor de la SubClase" );

// Llamamos al constructor de la superclase
super( bDato );
System.out.println(
"Dentro del constructor de la SubClase" );
}
}

class java508 {
public static void main( String args[] ) {
System.out.println( "Lanzando la aplicacion" );

// Instanciamos un objeto de este tipo llamando al
// constructor de defecto
java508 obj = new java508();
// Llamamos a la funcion pasandole un constructor de la
// subclase parametrizado como parametro
obj.miFuncion( new SubClase( 100 ) );
}

// Esta funcion recibe un objeto y llama a uno de sus metodos
// para presentar en pantalla el dato que contiene el objeto,
// en este caso el metodo es heredado de la SuperClase
void miFuncion( SubClase objeto ) {
objeto.verVarInstancia();
}
}
Si super no aparece como primera sentencia del cuerpo de un constructor, el compilador Java inserta una llamada implícita, super(), al constructor de la superclase inmediata. Es decir, el constructor por defecto de la superclase es invocado automáticamente cuando se ejecuta el constructor para una nueva subclase, si no se especifica un constructor parametrizado para llamar al constructor de la superclase.
Control de Acceso

El control de acceso también tiene un significado especial cuando se trata de constructores. Aunque en otra sección se trata a fondo el tela del control de acceso en Java, con referencia a los constructores se puede decir que el control de acceso que se indique determina la forma en que otros objetos van a pode instanciar objetos de la clase. En la siguiente descripción, se indica cómo se trata el control de acceso cuando se tienen entre manos a los constructores:
private
Ninguna otra clase puede instanciar objetos de la clase. La clase puede contener métodos públicos, y estos métodos pueden construir un objeto y devolverlo, pero nadie más puede hacerlo.
protected
Solamente las subclases de la clase pueden crear instancias de ella.
public
Cualquier otra clase puede crear instancias de la clase.
package
Nadie desde fuera del paquete puede construir una instancia de la clase. Esto es útil si se quiere tener acceso a las clases del paquete para crear instancias de la clase, pero que nadie más pueda hacerlo, con lo cual se restringe quien puede crear instancias de la clase.

En Java y en C++, una instancia de una clase, un objeto, contiene todas las variables y métodos de instancia de la clase y de todas sus superclases. Sin embargo, los dos lenguajes soportan la posibilidad de sobreescribir un método declarado en una superclase, indicando el mismo nombre y misma lista de argumentos; aunque los procedimientos para llevar a cabo esto son totalmente diferentes en Java y en C++.

Como aclaración a terminología que se empleo en este documento, quiero indicar que cuando digo sobrecargar métodos, quiero decir que Java requiere que los dos métodos tengan el mismo nombre, devuelvan el mismo tipo, pero tienen una diferente lista de argumentos. Y cuando digo sobreescribir métodos, quiero decir que Java requiere que los dos métodos tengan el mismo nombre, mismo tipo de retorno y misma lista de argumentos de llamada.

En Java, si una clase define un método con el mismo nombre, mismo tipo de retorno y misma lista de argumentos que un método de una superclase, el nuevo método sobreescribirá al método de la superclase, utilizándose en todos los objetos que se creen en donde se vea involucrado el tipo de la subclase que sobreescribe el método.
Finalizadores
Java no utiliza destructores (al contrario que C++) ya que tiene una forma de recoger automáticamente todos los objetos que se salen del alcance. No obstante proporciona un método que, cuando se especifique en el código de la clase, el reciclador de memoria (garbage collector) llamará: // Cierra el canal cuando este objeto es reciclado
protected void finalize() {
close();
}
Cada objeto tiene el método finalize(), que es heredado de la clase Object. Si se necesitase realizar alguna limpieza asociada con la memoria, se puede sobreescribir el método finalize() y colocar en él el código que sea necesario.

Los programadores C++ deben tener en cuenta que el método finalize() no es un destructor. En C++, si existe un destructor, éste será invocado siempre que el objeto se salga de ámbito o vaya a ser destruido. En Java, aunque el método finalize() siempre se invocará antes de que el reciclador de memoria libere la zona de memoria ocupada por el objeto, no hay garantía alguna de que el reciclador de memoria reclame la memoria de un determinado objeto, es decir, no hay garantía de que el método finalize() sea invocado.

La regla de oro a seguir es que no se debe poner ningún código que deba ser ejecutado en el método finalize(). Por ejemplo, si se necesita concluir la comunicación con un servidor cuando ya no se va a usar un objeto, no debe ponerse el código de desconexión en el método finalize(), porque puede que nunca se llamado. Luego, en Java, es responsabilidad del programador escribir métodos para realizar limpieza que no involucre a la memoria ocupada por el objeto y ejecutarlos en el instante preciso. El método finalize() y el reciclador de memoria son útiles para liberar la memoria de la pila y debería restringirse su uso solamente a eso, y no depender de ellos para realizar ningún otro tipo de limpieza.

No obstante, Java dispone de dos métodos para asegurar que los finalizadores se ejecuten. Los dos métodos habilitan la finalización a la salida de la aplicación, haciendo que los finalizadores de todos los objetos que tengan finalizador y que todavía no hayan sido invocados automáticamente, se ejecuten antes de que la Máquina Virtual Java concluya la ejecución de la aplicación. Estos dos métodos son:
runFinalizersOnExit( boolean ), método estático de java.lang.Runtime, y
runFinalizersOnExit( boolean ), método estático de java.lang.System
Una clase también hereda de sus superclase el método finalize(), y en caso necesario, debe llamarse una vez que el método finalize() de la clase haya realizado las tareas que se le hayan encomendado, de la forma: super.finalize();
En la construcción de un objeto, se desplaza uno por el árbol de jerarquía, de herencia, desde la raíz del árbol hacia las ramas, y en la finalización, es al revés, los desplazamientos por la herencia debe ser desde las ramas hacia las superclases hasta llegar a la clase raíz.

Control de Acceso

En C++ el control de acceso es menos complicado que en Java. Cualquier miembro individual de una clase en C++, puede ser designado como: private, public o protected.

Un miembro private solamente puede ser accedido por otros miembros de la propia clase; no puede ser accedido por miembros de una clase heredada. Es la designación más restrictiva de todas.

Un miembro designado como public puede ser accedido desde cualquier código dentro del ámbito de un objeto instanciado a partir de la clase. Es la designación menos restrictiva.

La designación de protected entra en juego solamente cuando se ve involucrada la herencia. Un miembro designado como protected aparece como public para los miembros de clases derivadas de la clase y aparece como private para todas las demás.

En C++ hay un segundo nivel de control de acceso con las mismas palabras reservadas, que no tiene analogía en Java, y que se aplica a nivel de la herencia de una clase desde otra clase.

También en C++, hay un aspecto adicional que son las funciones friend de una clase. Estas funciones tienen acceso a todos los miembros privados y protegidos de esa clase. Esta función puede ser una función miembro de otra clase o simplemente una función aislada (que no está soportada en Java). Java no tiene nada semejante a las funciones friend de C++.

El control de acceso se aplica siempre a nivel de clase, no a nivel de objeto. Es decir, los métodos de instancia de un objeto de una clase determinada tienen acceso directo a los miembros privados de cualquier otro objeto de la misma clase.

En Java, el control de acceso se complica un poco por la inclusión de la noción de package (paquete). Java implementa los mismos tres especificadores de acceso que C++ (aunque no necesariamente con el mismo significado) y, además, implementa ese cuarto especificador si no se ha indicado ninguno de los otros tres, que es el definido como package, default o friendly.

Por lo tanto, cuando se crea una nueva clase en Java, se puede especificar el nivel de acceso que se quiere para las variables de instancia y los métodos definidos en la clase: private, protected, public y package.

La tabla siguiente muestra el nivel de acceso que está permitido a cada uno de los especificadores:
Nivel de Acceso
clase
subclase
paquete
todos
Private
X



Protected
X
X*
X

Public
X
X
X
X
Package
X

X

Si se profundiza en el significado de la tabla, se puede observar que la columna clase indica que todos los métodos de una clase tienen acceso a todos los otros miembros de la misma clase, independientemente del nivel de acceso especificado.

La columna subclase se aplica a todas las clases heredadas de la clase, independientemente del paquete en que residan. Los miembros de una subclase tienen acceso a todos los miembros de la superclase que se hayan designado como public. El asterisco (*) en la intersección subclase-protected quiere decir que si una clase es a la vez subclase y está en el mismo paquete que la clase con un miembro protected, entonces la clase tiene acceso a es miembro protegido.

En general, si la subclase no se encuentra en el mismo paquete que la superclase, no tiene acceso a los miembros protegidos de la superclase. Los miembros de una subclase no tienen acceso a los miembros de la superclase catalogados como private o package, excepto a los miembros de una subclase del mismo paquete, que tienen acceso a los miembros de la superclase designados como package.

La columna paquete indica que las clases del mismo paquete tienen acceso a los miembros de una clase, independientemente de su árbol de herencia. La tabla indica que todos los miembros protected, public y package de una clase pueden ser accedidos por otra clase que se encuentre en el mismo paquete. Esto puede asemejarse un poco a la designación de funciones friend en C++, salvando las diferencias, que no son pocas.

En C++, se puede calificar un método en una clase diferente como friend de una clase determinada y ese status de friend no se extiende a ningún otro método de la clase, es decir, una persona de otra familia puede ser tu amigo, pero no por eso tienen que ser tus amigos el resto de los miembros de la familia de tu amigo.

En Java, colocando dos o más clases en el mismo paquete se hace que la relación friend, de amistad, se extienda a todos los métodos de las clases, es decir, si eres amigo de uno de los miembros de una familia, serás amigo automáticamente de todos y cada uno de los componentes de esa familia.

La columna todos indica que los privilegios de acceso para métodos que no están en la misma clase, ni en una subclase, ni en el mismo paquete, se encuentran restringidos a los miembros públicos de la clase.

Si se observa la misma tabla desde el punto de vista de las filas, podemos describir los calificadores de los métodos.
private private String NumeroDelCarnetDeIdentidad;
Las variables y métodos de instancia privados sólo pueden ser accedidos desde dentro de la clase. No son accesibles desde las subclases de esa clase. Hay que resaltar una vez más, que un método de instancia de un objeto de una clase puede acceder a todos los miembros privados de ese objeto, o miembros privados de cualquier otro objeto de la misma clase. Es decir, que en Java el control de acceso existe a nivel de clase, pero no a nivel de objeto de la clase.
public public void CualquieraPuedeAcceder(){}
Cualquier clase desde cualquier lugar puede acceder a las variables y métodos de instancia públicos.

protected protected void SoloSubClases(){}
Sólo las subclases de la clase y nadie más puede acceder a las variables y métodos de instancia protegidos. Los métodos protegidos pueden ser vistos por las clases derivadas, como en C++, y también, en Java, por los paquetes. Todas las clases de un paquete pueden ver los métodos protegidos de ese paquete. Esto difiere significativamente de C++, en donde los miembros protegidos solamente pueden ser accedidos por miembros de la misma clase o miembros de subclases.

package (fiendly, sin declaración específica) void MetodoDeMiPaquete(){}
Por defecto, si no se especifica el control de acceso, las variables y métodos de instancia se declaran package (friendly, amigas), lo que significa que son accesibles por todos los objetos dentro del mismo paquete, pero no por los externos al paquete. Aparentemente, parece lo mismo que protected; la diferencia estriba en que la designación de protected es heredada por las subclases de un paquete diferente, mientras que la designación package no es heredada por subclases de paquetes diferentes.

Debido a la complejidad y posible confusión respecto a los niveles de protección que proporciona Java para permitir el control preciso de la visibilidad de variables y métodos, se puede generar otra tabla en base a cuatro categorías de visibilidad entre los elementos de clase:

private
sin modificador
protected
public
Misma clase
SI
SI
SI
SI
Misma subclase de paquete
NO
SI
SI
SI
Misma no-subclase de paquete
NO
SI
SI
SI
Subclase de diferente paquete
NO
NO
SI
SI
No-subclase de diferente paquete
NO
NO
NO
SI
Y una guía de uso indicaría tener en cuenta lo siguiente:
Usar private para métodos y variables que solamente se utilicen dentro de la clase y que deberían estar ocultas para todo el resto
Usar public para métodos, constantes y otras variables importantes que deban ser visibles para todo el mundo
Usar protected si se quiere que las clases del mismo paquete puedan tener acceso a estas variables o métodos
Usar la sentencia package para poder agrupar las clases en paquetes
No usar nada, dejar la visibilidad por defecto (default, package) para métodos y variables que deban estar ocultas fuera del paquete, pero que deban estar disponibles al acceso desde dentro del mismo paquete. Utilizar protected en su lugar si se quiere que esos componentes sean visibles fuera del paquete
this

Al acceder a variables de instancia de una clase, la palabra clave this hace referencia a los miembros de la propia clase en el objeto actual; es decir, this se refiere al objeto actual sobre el que está actuando un método determinado y se utiliza siempre que se quiera hace referencia al objetoactual de la clase. Volviendo al ejemplo de MiClase, se puede añadir otro constructor de la forma siguiente: public class MiClase {
int i;
public MiClase() {
i = 10;
}
// Este constructor establece el valor de i
public MiClase( int valor ) {
this.i = valor; // i = valor
}
// Este constructor también establece el valor de i
public MiClase( int i ) {
this.i = i;
}
public void Suma_a_i( int j ) {
i = i + j;
}
}
Aquí this.i se refiere al entero i en la clase MiClase, que corresponde al objeto actual. La utilización de this en el tercer constructor de la clase, permite referirse directamente al objeto en sí, en lugar de permitir que el ámbito actual defina la resolución de variables, al utilizar i como parámetro formal y después this para acceder a la variable de instancia del objeto actual.
La utilización de this en dicho contexto puede ser confusa en ocasiones, y algunos programadores procuran no utilizar variables locales y nombres de parámetros formales que ocultan variables de instancia. Una filosofía diferente dice que en los métodos de inicialización y constructores, es bueno seguir el criterio de utilizar los mismos nombres por claridad, y utilizar this para no ocultar las variables de instancia. Lo cierto es que es más una cuestión de gusto personal que otra cosa el hacerlo de una forma u otra.

La siguiente aplicación de ejemplo, java509.java, utiliza la referencia this al objeto para acceder a una variable de instancia oculta para el método que es llamado.class java509 {
// Variable de instancia
int miVariable;

// Constructor de la clase
public java509() {
miVariable = 5;
}

// Metodo con argumentos
void miMetodo(int miVariable) {
System.out.println( "La variable Local miVariable contiene "
+ miVariable );
System.out.println(
"La variable de Instancia miVariable contiene "
+ this.miVariable );
}

public static void main( String args[] ) {
// Instanciamos un objeto del tipo de la clase
java509 obj = new java509();
// que utilizamos para llamar a su unico metodo
obj.miMetodo( 10 );
}
}
super

Si se necesita llamar al método padre dentro de una clase que ha reemplazado ese método, se puede hacer referencia al método padre con la palabra clave super: import MiClase;
public class MiNuevaClase extends MiClase {
public void Suma_a_i( int j ) {
i = i + ( j/2 );
super.Suma_a_i( j );
}
}
En el siguiente código, el constructor establecerá el valor de i a 10, después lo cambiará a 15 y finalmente el método Suma_a_i() de la clase padre MiClase lo dejará en 25: MiNuevaClase mnc;
mnc = new MiNuevaClase();
mnc.Suma_a_i( 10 );
super es un concepto que no existe en C++, al menos no con una implementación similar a Java. Si un método sobreescribe un método de su superclase, se puede utilizar la palabra clave super para eludir la versión sobreescrita de la clase e invocar a la versión original del método en la supreclase. Del mismo modo, se puede utilizar super para acceder a variables miembro de la superclase.

En el ejemplo java510.java, la aplicación utiliza super para referirse a una variable local en un método y a una variable de la superclase que tiene el mismo nombre. El programa también utiliza super para invocar al constructor de la superclase desde en constructor de la subclase.
Herencia

La herencia es el mecanismo por el que se crean nuevos objetos definidos en términos de objetos ya existentes. Por ejemplo, si se tiene la clase Ave, se puede crear la subclase Pato, que es una especialización de Ave. class Pato extends Ave {
int numero_de_patas;
}
La palabra clave extends se usa para generar una subclase (especialización) de un objeto. Un Pato es una subclase de Ave. Cualquier cosa que contenga la definición de Ave será copiada a la clase Pato, además, en Pato se pueden definir sus propios métodos y variables de instancia. Se dice que Pato deriva o hereda de Ave.
Además, se pueden sustituir los métodos proporcionados por la clase base. Utilizando nuestro anterior ejemplo de MiClase, aquí hay un ejemplo de una clase derivada sustituyendo a la función Suma_a_i(): import MiClase;
public class MiNuevaClase extends MiClase {
public void Suma_a_i( int j ) {
i = i + ( j/2 );
}
}
Ahora cuando se crea una instancia de MiNuevaClase, el valor de i también se inicializa a 10, pero la llamada al método Suma_a_i() produce un resultado diferente: MiNuevaClase mnc;
mnc = new MiNuevaClase();
mnc.Suma_a_i( 10 );
Java se diseñó con la idea de que fuera un lenguaje sencillo y, por tanto, se le denegó la capacidad de la herencia múltiple, tal como es conocida por los programadores C++. En este lenguaje se añade cierta complejidad sintáctica cuando se realiza herencia múltiple de varias clases, las cuales comparten una clase base común ( hay que declarar dicha clase como virtual y tener bastante cuidado con los constructores), o también cuando las clases base tienen miembros de nombre similar (entonces hay que utilizar especificadores de acceso).
Por ejemplo, de la clase aparato con motor y de la clase animal no se puede derivar nada, sería como obtener el objeto toro mecánico a partir de una máquina motorizada (aparato con motor) y un toro (aminal). En realidad, lo que se pretende es copiar los métodos, es decir, pasar la funcionalidad del toro de verdad al toro mecánico, con lo cual no sería necesaria la herencia múltiple sino simplemente la compartición de funcionalidad que se encuentra implementada en Java a través de interfaces.
Subclases

Como ya se ha indicado en múltiples ocasiones en esta sección, cuando se puede crear nuevas clases por herencia de clases ya existentes, las nuevas clases se llaman subclases, mientras que las clases de donde hereda se llaman superclases.
Cualquier objeto de la subclase contiene todas las variables y todos los métodos de la superclase y sus antecesores.

Todas las clases en Java derivan de alguna clase anterior. La clase raíz del árbol de la jerarquía de clases de Java es la clase Object, definida en el paquete java.lang. Cada vez que se desciende en el árbol de jerarquía, las clases van siendo más especializadas.

Cuando se desee que nadie pueda derivar de una clase, se indica que es final; y lo mismo con los métodos, si no se desea que se puedan sobreescribir, se les antepone la palabra clave final.
Lo contrario de final es abstract. Una clase marcada como abstracta, únicamente está diseñada para crear subclases a partir de ella, no siendo posible instanciar ningún objeto a partir de una clase abstracta.

Sobreescritura de Métodos

Para entender en su totalidad el código fuente escrito en Java, es imprescindible tener muy claro el concepto de la redefinición o sobreescritura de métodos. Tanto es así, que a continuación se vuelve a insistir sobre la cuestión, a pesar de haberla tratado varias veces a lo largo de este documento.

La sobreescritura de métodos es una característica más de la herencia en Java. Es decir, en Java las nuevas clases se pueden definir extendiendo clases ya existentes. Aquí surgen los conceptos de subclase que sería la clase obtenida, y superclase, que sería la clase que está siendo extendida, tal como también ya se ha explicado.

Cuando una nueva clase se extiende desde otra que ya existía, todas las variables y métodos que son miembros de la superclase (y todos aquellos miembros de los antecesores de la superclase) serán también miembros de la subclase.

En el supuesto de que en el entorno en que se va a mover la nueva subclase, alguno de los métodos de la superclase (o alguno de sus antecesores) no sea adecuado para los objetos originados de la subclase, es posible reescribir el método en la subclase para que se adapte en su funcionamiento a los objetos del tipo de la subclase.

La reescritura del método dentro de la subclase es lo que se conoce por sobreescritura de métodos (overriding methods). Todo lo que se requiere en Java para poder sobeescribir un método es utilizar el mismo nombre del método en la subclase, el mismo tipo de retorno y la misma lista de argumentos de llamada, y en el cuerpo de ese método en la subclase proporcionar un código diferente y específico para las acciones que vaya a realizar sobre objetos del tipo que origina la nueva subclase.

En C++ la cuestión es un poco más complicada, exigiendo, por ejemplo, que el método que vaya a ser sobreescrito esté originalmente definido como método virtual, lo que no es necesario en Java.
Aunque no se ha discutido todavía la capacidad de multihilo de Java, marcada por la clase Thread; si se puede indicar que hay un método en esta clase, run(), que es de vital importancia. La implementación de este método run() en la clase Thread está completamente vacía, indicando que no se hace nada sino que se limita a definir un método interfaz. No tiene sentido para el método run() definir nada por defecto, porque en último lugar será utilizado para realizar los que el hilo de ejecuíon necesite y los programadores de la librería de Java no pueden anticipar esas necesidades.

Pero, por otro lado, este método no se puede declarar como abstracto, porque esto podría influir en la instanciación de objetos de la clase Thread, y la instanciación de estos objetos es un aspecto sumamente crítico en la programación multihilo en Java. Luego el resultado ha sido que los programadores de Sun han definido a run() como un método vacío.

Se puede reemplazar completamente la implementación de un método heredado indicando el mismo nombre, la misma lista de argumentos y el mismo tipo de retorno; y colocar en el cuerpo del método el código que realice la función que sea menester en su nueva situación. En el caso del método run(), se podría hacer algo como: class MiThread extends Thread {
void run() {
// código del método
}
}
En este fragmento de código, el método run() de la clase MiThread sobreescribe al método run() de la clase Thread y proporciona una nueva implementación.

Hay que tener cuidado con la diferencia entre sobrecargar y sobreescribir un método, y entenderla correctamente. Para sobrecargar un método hay que duplicar el nombre el método y el tipo que devuelve, pero utilizar una lista de argumentos diferente al original. Para sobreescribir un método, no solamente el nombre y el tipo de retorno deben ser iguales, sino que ha de serlo también la lista de argumentos. Es decir, que hay que estar atentos a la lista de argumentos que se indica para un método, en evitación de la sobrecarga cuando en realidad se cree que se está sobreescribiendo, o viceversa.

Clase Object
Anterior Siguiente
La clase Object, como ya se ha indicado anteriormente, es la clase raíz de todo el árbol de la jerarquía de clases Java, y proporciona un cierto número de métodos de utilidad general que pueden utilizar todos los objetos. La lista completa se puede ver en la documentación del API de Java, aquí solamente se tratarán algunos de ellos; por ejemplo, Object proporciona:
Un método por el que un objeto se puede comparar con otro objeto
Un método para convertir un objeto a una cadena
Un método para esperar a que ocurra una determinada condición
Un método para notificar a otros objetos que una condición ha cambiado
Un método para devolver la clase de un objeto
El método equals() public boolean equals( Object obj );
Todas las clases que se definan en Java heredarán el método equals(), que se puede utilizar para comparar dos objetos. Esta comparación no es la misma que proporciona el operador ==, que solamente compara si dos referencias a objetos apuntan al mismo objeto.
El método equals() se utiliza para saber si dos objetos separados son del mismo tipo y contienen los mismos datos. El método devuelve true si los objetos son iguales y false en caso contrario.
El sistema ya sabe de antemano cómo aplicar el método a todas las clases estándar y a todos los objetos de los que el compilador tiene conocimiento. Por ejemplo, se puede usar directamente para saber si dos objetos String son iguales.
Las subclases pueden sobreescribir el método equals() para realizar la adecuada comparación entre dos objetos de un tipo que haya sido definido por el programador. En la aplicación de ejemplo siguiente,
java511.java, se sobreescribe el método para comparar dos objetos de la nueva clase que crea la aplicación.
Hay que observar que en la lista de argumentos del método equals() hay que pasarle un argumento de tipo Object. Si se define un método con un argumento de tipo diferente, se estará sobrecargando el método, no sobreescribiéndolo.
En el ejemplo, una vez que se ejecuta, es necesario hacer un moldeo del argumento al tipo de la clase antes de intentar realizar la comparación. Se utiliza el operador instanceof para confirmar que el objeto es del tipo correcto. Uno de los objetos proporcionados para comprobar su equivalencia es de tipo erróneo (String) y el método sobreescrito equals() indicará que no es un objeto equivalente.class java511 {
int miDato;

// Constructor parametrizado
java511( int dato ) {
miDato = dato;
}

public static void main(String args[] ) {
// Se instancian los objetos que se van a testear
java511 obj1 = new java511( 2 );
java511 obj2 = new java511( 2 );
java511 obj3 = new java511( 3 );
String obj4 = "Un objeto String";

// Se realizan las comprobaciones y se presenta por pantalla
// el resultado de cada una de ellas
System.out.println( "obj1 equals obj1: " +
obj1.equals( obj1 ) );
System.out.println( "obj1 equals obj2: " +
obj1.equals( obj2 ) );
System.out.println( "obj1 equals obj3: " +
obj1.equals( obj3 ) );
System.out.println( "obj1 equals obj4: " +
obj1.equals( obj4 ) );
}

// Se sobreescribe el metodo equals()
public boolean equals( Object arg ) {
// Se comprueba que el argumento es del tipo adecuado y
// que no es nulo. Si lo anterior se cumple se realiza
// la comprobacion de equivalencia de los datos.
// Observese que se ha empleado el operador instanceof
if( (arg != null) && (arg instanceof java511) ) {
// Hacemos un moldeado del Object general a tipo java511
java511 temp = (java511)arg;
// Se realiza la comparacion y se devuelve el resultado
return( this.miDato == temp.miDato );
}
else {
// No es del tipo esperado
return( false );
}
}
}
El método getClass()public final native Class getClass();
En Java existe la clase Class, que se define de la forma (la declaración no está completa, consultar el API de Java para ello):public final class java.lang.Class extends java.lang.Object {
// Métodos
public static Class forName(String className);
public ClassLoader getClassLoader();
public Class[] getClasses();
public Class getComponentType();
public Constructor getConstructor(Class[] parameterTypes);
public Constructor[] getConstructors();
public Class[] getDeclaredClasses();
public Constructor[] getDeclaredConstructors();
public Field getDeclaredField(String name);
public Field[] getDeclaredFields();
public Method getDeclaredMethod(String name, Class[] parTypes);
public Method[] getDeclaredMethods();
public Class getDeclaringClass();
public Field getField(String name);
public Field[] getFields();
public Class[] getInterfaces();
public Method getMethod(String name, Class[] parameterTypes);
public Method[] getMethods();
public int getModifiers();
public String getName();
public URL getResource(String name);
public InputStream getResourceAsStream(String name);
public Object[] getSigners();
public Class getSuperclass();
public boolean isArray();
public boolean isAssignableFrom(Class cls);
public boolean isInstance(Object obj);
public boolean isInterface();
public boolean isPrimitive();
public Object newInstance();
public String toString(); // Overrides Object.toString()
}
Instancias de la clase Class representan las clases e interfaces que está ejecutando la aplicación Java. No hay un constructor para la clase Class, sus objetos son construidos automáticamente por la Máquina Virtual Java (JVM) cuando las clases son cargadas, o por llamadas al método defineClass() del cargados de clases.
Es una clase importante porque se le pueden realizar peticiones de información sobre objetos, como cuál es su nombre o cómo se llama su superclase.
El método getClass() de la clase Object se puede utilizar para determinar la clase de un objeto. Es decir, devuelve un objeto de tipo Class, que contiene información importante sobre el objeto que crea la clase. Una vez determinada la clase del objeto, se pueden utilizar los métodos de la clase Class para obtener información acerca del objeto.
Además, habiendo determinado la clase del objeto, el método newInstance() de la clase Class puede invocarse para instanciar otro objeto del mismo tipo. El resultado es que el operador new será utilizado con un constructor de una clase conocida.
Hay que hacer notar que la última afirmación del párrafo anterior es una situación que el compilador no conoce en tiempo de compilación, es decir, no sabe el tipo del objeto que va a ser instanciado. Por lo tanto, si se necesita referenciar al nuevo objeto, es necesario declarar la variable de referencia del tipo genérico Object, aunque el objeto actual tomará todos los atributos de la subclase actual por la que será instanciado.
Hay que hacer notar que el método getClass() es un método final y no puede ser sobreescrito. Devuelve un objeto de tipo Class que permite el uso de los métodos definidos en la clase Class sobre ese objeto.
El siguiente programa,
java512.java, ilustra alguna de estas características. Primero, instancia un objeto, mira la clase de ese objeto y utiliza alguno de los métodos de la clase Class para obtener y presentar información acerca del objeto. Luego, pregunta al usuario si quiere instanciar un nuevo objeto, instanciando un objeto de tipo String en un caso o, en el otro caso, se aplica el método getClass() a un objeto existente y utilizando el método newInstance() se instancia un nuevo objeto del mismo tipo.import java.io.*;
class java512 {
public static void main( String args[] ) {
java512 obj1 = new java512();
// Se usa el metodo getClass() de la clase Object y dos
// metodos de la clase Class para obtener y presentar
// informacion acerca del objeto
System.out.println( "Nombre de la clase de obj1: "
+ obj1.getClass().getName() );
System.out.println( "Nombre de la superclase de obj1: "
+ obj1.getClass().getSuperclass() );

// Ahora se instancia otro objeto basandose en la entrada
// del usuario, de forma que el compilador no tiene forma
// de conocer el tipo del objeto en tiempo de compilacion
// Se declara una referencia a un objeto generico
Object obj2 = null;

// Se pide la entrada al usuario
System.out.println( "Introduce un 0 o un 1" );
try {
// Captura la entrada del usuario
int dato = System.in.read();
// Si se ha introducido un 0
if( (char)dato == '0' )
// Se crea un objeto String
obj2 = "Esto es un objeto String";
// Sino, se crea un nuevo objeto del mismo tipo
// que el anterior
else
obj2 = obj1.getClass().newInstance();
} catch( Exception e ) {
System.out.println( "Excepcion " + e );
}

// Ahora se indican la clase y superclase del nuevo objeto
System.out.println( "Nombre de la clase de obj2: "
+ obj2.getClass().getName() );
System.out.println( "Nombre de la superclase de obj2: "
+ obj2.getClass().getSuperclass());
}
}
El método toString()public String toString();
La clase Object dispone de este método que puede usarse para convertir todos los objetos conocidos por el compilador a algún tipo de representación de cadena, que dependerá del objeto.
Por ejemplo, el método toString() extrae el entero contenido en un objeto Integer. De forma similar, si se aplica el método toString() al objeto Thread, se puede obtener información importante acerca de los threads y presentarla como cadena.
Este método también se puede sobreescribir, o redefinir, para convertir los objetos definidos por el programador a cadenas. El programa siguiente,
java513.java, redefine el método toString() de una clase recién definida para que pueda utilizarse en la conversión de objetos de esta clase a cadenas.class java513 {
// Se definen las variables de instancia para la clase
String uno;
String dos;
String tres;
// Constructor de la clase
java513( String a,String b,String c ) {
uno = a;
dos = b;
tres = c;
}

public static void main( String args[] ) {
// Se instancia un objeto de la clase
java513 obj = new java513( "Tutorial","de","Java" );

// Se presenta el objeto utilizando el metodo sobreescrito
System.out.println( obj.toString() );
}
// Sobreescritura del metodo toString() de la clase Object
public String toString() {
// Convierte un objeto a cadena y lo devuelve
return( uno+" "+dos+" "+tres );
}
}
Otros Métodos
Hay otros métodos útiles en la clase Object que son, o serán, discutidos en otras secciones de este documento. Por ejemplo, el métodoprotected void finalize();
que se tratará en el apartado de finalizadores. O también, los métodos que se utilizan en la programación de threads para hacer que varios threads se sincronicen, como son public final void wait();
public final native void wait( long timeout );
public final native void notify();
public final native void notifyAll();
que se tratarán cuando se hable del multithreading en Java.

Clases Abstractas

Una de las características más útiles de cualquier lenguaje orientado a objetos es la posibilidad de declarar clases que definen como se utiliza solamente, sin tener que implementar métodos, son las clases abstractas. Mediante una clase abstracta se intenta fijar un conjunto mínimo de métodos (el comportamiento) y de atributos, que permitan modelar un cierto concepto, que será refinado y especializado mediante el mecanismo de la herencia. Como consecuencia, la implementación de la mayoría de los métodos de una clase abstracta podría no tener significado. Para resolver esto, Java proporciona los métodos abstractos. Estos métodos se encuentran incompletos, sólo cuentan con la declaración y no poseen cuerpo de definición. Esto es muy útil cuando la implementación es específica para cada usuario, pero todos los usuarios tienen que utilizar los mismos métodos. Un ejemplo de clase abstracta en Java es la clase Graphics: public abstract class Graphics {
public abstract void drawLine( int x1,int y1,int x2,int y2 );
public abstract void drawOval( int x,int y,int width,int height );
public abstract void drawArc( int x,int y,int width,
int height,int startAngle,int arcAngle );
. . .
}
Los métodos se declaran en la clase Graphics, pero el código que ejecutará el método está en algún otro sitio: public class MiClase extends Graphics {
public void drawLine( int x1,int y1,int x2,int y2 ) {

}
}
Cuando una clase contiene un método abstracto tiene que declararse abstracta. No obstante, no todos los métodos de una clase abstracta tienen que ser abstractos. Las clases abstractas no pueden tener métodos privados (no se podrían implementar) ni tampoco estáticos. Una clase abstracta tiene que derivarse obligatoriamente, no se puede hacer un new de una clase abstracta.
Una clase abstracta en Java es lo mismo que en C++ virtual func() = 0;
lo que obliga a que al derivar de la clase haya que implementar forzosamente los métodos de esa clase abstracta.

Interfaces

Los métodos abstractos son útiles cuando se quiere que cada implementación de la clase parezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los métodos abstractos. Los interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior, lo que permite simular la herencia múltiple de otros lenguajes.

Un interfaz sublima el concepto de clase abstracta hasta su grado más alto. Un interfaz podrá verse simplemente como una forma, es como un molde, solamente permite declarar nombres de métodos, listas de argumentos, tipos de retorno y adicionalmente miembros datos (los cuales podrán ser únicamente tipos básicos y serán tomados como constantes en tiempo de compilación, es decir, static y final).

Un interfaz contiene una colección de métodos que se implementan en otro lugar. Los métodos de una clase son public, static y final.

La principal diferencia entre interface y abstract es que un interfaz proporciona un mecanismo de encapsulación de los protocolos de los métodos sin forzar al usuario a utilizar la herencia. Por ejemplo: public interface VideoClip {
// comienza la reproduccion del video
void play();
// reproduce el clip en un bucle
void bucle();
// detiene la reproduccion
void stop();
}
Las clases que quieran utilizar el interfaz VideoClip utilizarán la palabra implements y proporcionarán el código necesario para implementar los métodos que se han definido para el interfaz: class MiClase implements VideoClip {
void play() {

}
void bucle() {

}
void stop() {

}
Al utilizar implements para el interface es como si se hiciese una acción de copiar-y-pegar del código del interface, con lo cual no se hereda nada, solamente se pueden usar los métodos.
La ventaja principal del uso de interfaces es que una clase interface puede ser implementada por cualquier número de clases, permitiendo a cada clase compartir el interfaz de programación sin tener que ser consciente de la implementación que hagan las otras clases que implementen el interface. class MiOtraClase implements VideoClip {
void play() {

}
void bucle() {

}
void stop() {

}
Es decir, el aspecto más importante del uso de interfaces es que múltiples objetos de clases diferentes pueden ser tratados como si fuesen de un mismo tipo común, donde este tipo viene indicado por el nombre del interfaz.

Aunque se puede considerar el nombre del interfaz como un tipo de prototipo de referencia a objetos, no se pueden instanciar objetos en sí del tipo interfaz. La definición de un interfaz no tiene constructor, por lo que no es posible invocar el operador new sobre un tipo interfaz.
Un interfaz puede heredar de varios interfaces sin ningún problema. Sin embargo, una clase solamente puede heredar de una clase base, pero puede implementar varios interfaces. También, el JDK ofrece la posibilidad de definir un interfaz vacío, como es el caso de Serialize, que permite serializar un objeto. Un interfaz vacío se puede utilizar como un flag, un marcador para marcar a una clase con una propiedad determinada.

La aplicación java514.java, ilustra algunos de los conceptos referentes a los interfaces. Se definen dos interfaces, en uno de ellos se definen dos constantes y en el otro se declara un método put() y un método get(). Las constantes y los métodos se podrían haber colocado en la misma definición del interfaz, pero se han separado para mostrar que una clase simple puede implementar dos o más interfaces utilizando el separador coma (,) en la lista de interfaces.

También se definen dos clases, implementando cada una de ellas los dos interfaces. Esto significa que cada clase define el método put() y el método get(), declarados en un interfaz y hace uso de las constantes definidas en el otro interfaz. Estas clase se encuentran en ficheros separados por exigencias del compilador, los ficheros son Constantes.java y MiInterfaz.java, y el contenido de ambos ficheros es el que se muestra a continuación:public interface Constantes {
public final double pi = 6.14;
public final int constanteInt = 125;
}

public interface MiInterfaz {
void put( int dato );
int get();
}

Es importante observar que en la definición de los dos métodos del interfaz, cada clase los define de la forma más adecuada para esa clase, sin tener en cuenta cómo estará definidos en las otras clases.

Una de las clases también define el método show(), que no está declarado en el interfaz. Este método se utiliza para demostrar que un método que no está declarado en el interfaz no puede ser accedido utilizando una variable referencia de tipo interfaz.

El método main() en la clase principal ejecuta una serie de instanciaciones, invocaciones de métodos y asignaciones destinadas a mostrar las características de los interfaces descritos anteriormente. Si se ejecuta la aplicación, las sentencias que se van imprimiendo en pantalla son autoexplicactivas de lo que está sucediendo en el corazón de la aplicación.

Los interfaces son útiles para recoger las similitudes entre clase no relacionadas, forzando una relación entre ellas. También para declarar métodos que forzosamente una o más clases han de implementar. Y también, para tener acceso a un objeto, para permitir el uso de un objeto sin revelar su clase, son los llamados objetos anónimos, que son muy útiles cuando se vende un paquete de clases a otros desarrolladores.

C++ permite al programador crear nuevas clases como una subclase de dos o más superclases diferentes, es la herencia múltiple. Java no permite herencia múltiple. Hay autores que dicen que el interfaz de Java es un sustituto de la herencia múltiple y hay otros que están en desacuerdo con eso. Lo cierto es que sí parece una alternativa y los interfaces resuelven algunos de los problemas de la herencia múltiple, por ejemplo:
No se pueden heredar variables desde un interfaz
No se pueden heredar implementaciones de métodos desde un interfaz
La jerarquía de un interfaz es independiente de la jerarquía de clases que implementen el mismo interfaz
y esto no es cierto en la herencia múltiple, tal como se ve desde C++.
Definición

La definición de un interfaz es semejante a la de una clase. La definición de interfaz tiene dos componentes, declaración y cuerpo. En forma esquemática sería: DeclaracionInterfaz {
// cuerpoInterfaz
}
Declaración

La mínima declaración consiste en la palabra clave interface y el nombre del interfaz. Por convenio, los nombres de interfaces comienzan con una letra mayúscula, como los nombres de las clases, pero no es obligatorio.

La declaración de interfaz puede tener dos componentes adicionales, el especificador de acceso public y la lista de superinterfaces.

Un interfaz puede extender otros interfaces. Sin embargo, mientras que una clase solamente puede extender otra clase, un interfaz puede extender cualquier número de interfaces. En el ejemplo se muestra la definición completa de un interfaz, declaración y cuerpo.public interface MiInterfaz extends InterfazA,InterfazB {
public final double PI = 3.14159;
public final int entero = 125;
void put( int dato );
int get();
}
El especificador de acceso public indica que el interfaz puede ser utilizado por cualquier clase de cualquier paquete. Si se omite, el interfaz solamente será accesible a aquellas clases que estén definidas en el mismo paquete.

La cláusula extends es similar a la de la declaración de clase, aunque un interfaz puede extender múltiples interfaces. Un interfaz no puede extender clases.

La lista de superinterfaces es un lista separada por comas de todas las interfaces que la nueva interfaz va a extender. Un interfaz hereda todas las constantes y métodos de sus superinterfaces, excepto si el interfaz oculta una constante con otra del mismo nombre, o si redeclara un método con una nueva declaración de ese método.

El cuerpo del interfaz contiene las declaraciones de los métodos, que terminan en un punto y coma y no contienen código alguno en su cuerpo. Esto es semejante al prototipo de funciones en C++. Todos los métodos declarados en un interfaz son implícitamente, public y abstract, y no se permite el uso de transient, volatile, private, protected o syncronized en la declaración de miembros en un interfaz. En el cuerpo del interfaz se pueden definir constantes, que serán implícitamente public, static y final.
Implementación
Un interfaz se utiliza definiendo una clase que implemente el interfaz a través de su nombre. Cuando una clase implementa un interfaz, debe proporcionar la definición completa de todos los métodos declarados en el interfaz y, también, la de todos los métodos declarados en todos los superinterfaces de ese interfaz.

Una clase puede implementar más de un interfaz, incluyendo varios nombre de interfaces separados por comas. En este caso, la clase debe proporcionar la definición completa de todos los métodos declarados en todos los interfaces de la lista y de todos los superinterfaces de esos interfaces.

En el anterior ejemplo, java514.java, se puede observar una clase que implementa dos interfaces, Constantes y MiInterfaz. class ClaseA implements Constantes,MiInterfaz {
double dDato;
// Define las versiones de put() y get() que utiliza la ClaseA
public void put( int dato ) {
// Se usa "pi" del interfaz Constantes
dDato = (double)dato * pi;
System.out.println(
"En put() de ClaseA, usando pi del interfaz " +
"Constantes: " + dDato );
}
public int get() {
return( (int)dDato );
}
// Se define un metodo show() para la ClaseA que no esta
// declarado en el interfaz MiInterfaz
void show() {
System.out.println(
"En show() de ClaseA, dDato = " + dDato );
}
}
Como se puede observar, esta clase proporciona la definición completa de los métodos put() y get() del interfaz MiInterfaz, a la vez que utiliza las constantes definidas en el interfaz Constantes. Además, la clase proporciona la definición del método show() que no está declarado en ninguno de los interfaces, sino que es propio de la clase.

La definición de un interfaz es una definición de un nuevo tipo de datos. Se puede utilizar el nombre del interfaz en cualquier lugar en que se pueda utilizar un nombre de tipo de dato. Sin embargo, no se pueden instanciar objetos del tipo interfaz, porque un interfaz no tiene constructor. En esta sección, hay numerosos ejemplos de uso del nombre de un interfaz como tipo.
Herencia "Multiple"

Un interfaz no es solamente una forma más pura de denominar a una clase abstracta, su propósito es mucho más amplio que eso. Como un interfaz no tiene ninguna implementación, es decir, no hay ningun tipo de almacenamiento asociado al interfaz, no hay nada que impida la combinación de varios interfaces. Esto tiene mucho valor porque hay veces en que es necesario que un objeto X sea a y b y c. En C++ es donde esto se acuñó como herencia multiple y no es sencillo de hacer porque cada clase puede tener su propia implementación. En Java, se puede hacer lo mismo, pero solamente una de las clases puede tener implementeación, con lo cual los problemas que se presentan en C++ no suceden con Java cuando se combinan multiples interfaces.

En una clase derivada, el programador no está forzado a tener una clase base sea esta abstracta o concreta, es decir, una sin métodos abstractos. Si se hereda desde una clase no interfaz, solamente se puede heredar de una. El resto de los elementos base deben ser interfaces. Todos los nombres de interfaces se colocan después de la palabra clave implements y separados por comas. Se pueden tener tantos interfaces como se quiera y cada uno de ellos será un tipo independiente. El ejemplo siguiente, java516.java, muestra una clase concreta combinada con varios interfaces para producir una nueva claseimport java.util.*;
interface Luchar {
void luchar();
}
interface Nadar {
void nadar();
}
interface Volar {
void volar();
}
class Accion {
public void luchar() {}
}

class Heroe extends Accion implements Luchar,Nadar,Volar {
public void nadar() {}
public void volar() {}
}

public class java516 {
static void a( Luchar x ) {
x.luchar();
}
static void b( Nadar x ) {
x.nadar();
}
static void c( Volar x ) {
x.volar();
}
static void d( Accion x ) {
x.luchar();
}

public static void main( String args[] ) {
Heroe i = new Heroe();
a( i ); // Trata al Heroe como Luchar
b( i ); // Trata al Heroe como Nadar
c( i ); // Trata al Heroe como Volar
d( i ); // Trata al Heroe como Accion
}
}
Se puede observar que Heroe combina la clase concreta Accion con los interfaces Luchar, Nadar y Volar. Cuando se combina la clase concreta con interfaces de este modo, la clase debe ir la primera y luego los interfaces; en caso contrario, el compilador dará un error.

La autentificación, signature, para luchar() es la misma en el interfaz Luchar y en la clase Accion, y luchar() no está proporcionado con la definicion de Heroe. Si se quiere crear un objeto del nuevo tipo, la clase debe tener todas las definiciones que necesita, y aunque Heroe no proporciona una definicion explicita para luchar(), la definición es proporcionada automáticamente por Accion y así es posible crear objetos de tipo Heroe.

En la clase java516, hay cuatro métodos que toman como argumentos los distintos interfaces y la clase concreta. Cuando un objeto Heroe es creado, puede ser pasado a cualquierea de estos métodos, lo que significa que se está realizando un upcasting al interfaz de que se trate. Debido a la forma en que han sido diseñados los interfaces en Java, esto funciona perfectamente sin ninguna dificultad ni esfuerzo extra por parte del programador.

La razón principal de la existencia de los interfaces en el ejemplo anterior es el proder realizar un upcasting a más de un tipo base. Sin embargo, hay una segunda razón que es la misma por la que se usan clases abstractas: evitar que el programador cliente tenga que crear un objeto de esta clase y establecer que solamente es un interfaz. Y esto plantea la cuestión de qué se debe utilizar pues, un interfaz o una clase abastacta. Un interfaz proporciona los beneficios de una clase abstracta y, además, los beneficios propios del interfaz, así que es posible crear una clase base sin la definición de ningún método o variable miembro, por lo que se deberían utilizar mejor los interfaces que las clases abstractas. De hecho, si se desea tener una clase base, la primera elección debería ser un interfaz, y utilizar clases abstractas solamente si es necesario tener alguna variable miembro o algun método implementado.

Métodos Nativos

Java proporciona un mecanismo para la llamada a funciones C y C++ desde el código fuente Java. Para definir métodos como funciones C o C++ se utiliza la palabra clave native. public class Fecha {
int ahora;
public Fecha() {
ahora = time();
}
private native int time();
static {
System.loadLibrary( "time" );
}
}
Una vez escrito el código Java, se necesitan ejecutar los pasos siguientes para poder integrar el código C o C++:
Utilizar javah para crear un fichero de cabecera (.h)
Utilizar javah para crear un fichero de stubs, es decir, que contiene la declaración de las funciones

Escribir el código del método nativo en C o C++, es decir, rellenar el código de la función, completando el trabajo de javah al crear el fichero de stubs
Compilar el fichero de stubs y el fichero .c en una librería de carga dinámica (DLL en Windows '95 o libXX.so en Unix)
Ejecutar la aplicación con el appletviewer
En otra sección se tratan en profundidad los métodos nativos, porque añaden una gran potencia a Java, al permitirle integrar a través de librería dinámica cualquier algoritmo desarrollado en C o C++, lo cual, entre otras cosas, se utiliza como método de protección contra la descompilación completa del código Java.

Paquetes
Para explicar el tema de los paquetes imaginarse una ciudad en la cual hay varios bloques de apartamentos propiedad de una única empresa inmobiliaria. Esta empresa dispone además de comercios, zonas de recreo y almacenes. Se puede pensar en la empresa como una lista de referencias a cada una de sus propiedades; es decir, la inmobiliaria sabe exactamente donde está un apartamento determinado y puede hacer uso de él en el momento en que lo necesite.
Si ahora se mira lo anterior en términos de Java, la empresa inmobiliaria es el paquete. Los paquetes agrupan a librerías de clases, como las librerías que contienen información sobre distintas propiedades comerciales. Un paquete será, pues, la mayor unidad lógica de objetos en Java.

Los paquetes se utilizan en Java de forma similar a como se utilizan las librerías en C++, para agrupar funciones y clases, sólo que en Java agrupan diferentes clases y/o interfaces. En ellos las clases son únicas, comparadas con las de otros paquetes, y además proporcionan un método de control de acceso. Los paquetes también proporcionan una forma de ocultar clases, evitando que otros programas o paquetes accedan a clases que son de uso exclusivo de una aplicación determinada.
Declaración de Paquetes

Los paquetes se declaran utilizando la palabra package seguida del nombre del paquete. Esto debe estar al comienzo del fichero fuente, en concreto, debe ser la primera sentencia ejecutable del código Java, excluyendo, claro está, los comentarios y espacios en blanco. Por ejemplo:package mamiferos;
class Ballena {
. . .
}
En este ejemplo, el nombre del paquete es mamiferos. La clase Ballena se considera como parte del paquete. La inclusión de nuevas clases en el paquete es muy sencilla, ya que basta con colocar la misma sentencia al comienzo de los ficheros que contengan la declaración de las clases. Como cada clase se debe colocar en un fichero separado, cada uno de los ficheros que contengan clases pertenecientes a un mismo paquete, deben incluir la misma sentencia package, y solamente puede haber una sentencia package por fichero.

Se recuerda que el compilador Java solamente requiere que se coloquen en ficheros separados las clases que se declaren públicas. Las clases no públicas se pueden colocar en el mismo fichero fuente, al igual que las clases anidadas. Aunque es una buena norma de programación que todas las clases se encuentren en un único fichero, la sentencia package colocada el comienzo de un fichero fuente afectará a todas las clases que se declaren en ese fichero.

Java también soporta el concepto de jeraquía de paquetes. Esto es parecido a la jerarquía de directorios de la mayoría de los sitemas operativos. Se consigue especificando múmtiples nombres en la sentencia package, separados por puntos. Por ejemplo, en las sentencias siguientes, la clase Ballena pertenece al paquete mamiferos que cae dentro de la jerarquía del paquete animales.package animales.mamiferos;
class Ballena {
. . .
}
Esto permite agrupar clases relacionadas en un solo paquete, y agrupar paquetes relacionados en un paquete más grande. Para referenciar a un miembro de otro paquete, se debe colocar el nombre del paquete antes del nombre de la clase. La siguiente sentencia es un ejemplo de llamada al método obtenerNombre() de la clase Ballena que pertenece al subpaquete mamiferos del paquete animales:animales.mamiferos.Ballena.obtenerNombre();
La analogía con la jerarquía de directorios se ve reforzada por el intérprete Java, ya que éste requiere que los ficheros .class se encuentren físicamente localizados en subdirectorios que coincidan con el nombre del subpaquete. En el ejemplo anterior, si se encontrase en una máquina Unix, la clase Ballena debería estar situada en el camino siguiente:animales/mamiferos/Ballena.class

Por supuesto, las convenciones en el nombre de los directorios serán diferentes para los distintos sistemas operativos. El compilador Java colocará los ficheros .class en el mismo directorio que se encuentren los ficheros fuentes, por lo que puede ser necesario mover los ficheros .class resultantes de la compilación al directorio adecuado, en el caso de que no se encuentren los fuentes en el lugar correcto del árbol jerárquico. Aunque los ficheros .class también se pueden colocar directamente en el directorio que se desee especificando la opción -d (directorio) a la hora de invocar al compilador. La siguiente línea de comando colocará el fichero resultante de la compilación en el subdirectorio animales/mamiferos/Ballenas, independientemente de cual sea el directorio desde el cual se esté invocando al compilador.> javac -d animales/mamiferos/Ballena Ballena.java

Todas las clases quedan englobadas dentro de un mismo paquete, si no se especifica explíctamente lo contrario, es decir, aunque no se indique nada, las clases pertenecen a un paquete; ya que, como es normal en Java, lo que no se declara explícitamente, toma valores por defecto. En este caso, hay un paquete sin nombre que agrupa a todos los demás paquetes. Si un paquete no tiene nombre, no es posible para los demás paquetes referenciar a ese paquete, por eso es conveniente colocar todas las clases no triviales en paquetes, para que puedan ser referenciadas posteriormente desde cualquier otro programa.

Acceso a Otros Paquetes

Se decía que se pueden referenciar paquetes precediendo con su nombre la clase que se quiere usar. También se puede emplear la palabra clave import, si se van a colocar múltiples referencias a un mismo paquete, o si el nombre del paquete es muy largo o complicado.
La sentencia import se utiliza para incluir una lista de paquetes en los que buscar una clase determinada, y su sintaxis es:import nombre_paquete;
Esta sentencia, o grupo de ellas, deben aparecer antes de cualquier declaración de clase en el código fuente. Por ejemplo:import animales.mamiferos.Ballena;
En este ejemplo, todos los miembros (variables, métodos) de la clase Ballena están accesibles especificando simplemente su nombre, sin tener que precederlo del nombre completo del paquete.

Esta forma de abreviar tienes sus ventajas y sus desventajas. La ventaja principal es que el código no se vuelve demasiado difícil de leer y además es más rápido de teclear. La desventaja fundamental es que resulta más complicado el saber exactamente a qué paquete pertenece un determinado miembro; y esto es especialmente complicado cuando hay muchos paquetes importados.

En la sentencia import también se admite la presencia del carácter *, asterisco. Cuando se emplea, se indica que toda la jerarquía de clases localizada a partir del punto en que se encuentre, debe ser importada, en lugar de indicar solamente una determinada clase. Por ejemplo, la siguiente sentencia indicaría que todas la clases del subpaquete animales.mamiferos, deben ser importadas:import animales.mamiferos.*;

Esta es una forma simple y sencilla de tener acceso a todas las clases de un determinado paquete. Aunque el uso del asterisco debe hacerse con cautela, porque al ya de por sí lento compilador, si se pone un asterisco, se cargarán todos los paquetes, lo que hará todavía más lenta la compilación. No obstante, el asterisco no tiene impacto alguno a la hora de la ejecución, solamente en tiempo de compilación.

La sentencia import se utiliza en casi todos los ejemplos del Tutorial, fundamentalmente para acceder a las distintas partes del API de Java. Por defecto, el conjunto de clases bajo java.lang.* se importan siempre; las otras librerías deben ser importadas explícitamente. Por ejemplo, las siguientes líneas de código premiten el acceso a las clases correspondientes a las librerías de manipulacion de imágenes y gráficos:import java.awt.Image;
import java.awt.Graphics;
Nomenclatura de Paquetes

Los paquetes pueden nombrarse de cualquier forma que siga el esquema de nomenclatura de Java. Por convenio, no obstante, los nombres de paquetes comienzan por una letra minúscula para hacer más sencillo el reconocimiento de paquetes y clases, cuando se tiene una referencia explícita a una clase. Esto es porque los nombres de las clases, también por convenio, empiezan con una letra mayúscula. Por ejemplo, cuando se usa el convenio citado, es obvio que tanto animales como mamiferos son paquetes y que Ballena es una clase. Cuanquier cosa que siga al nombre de la clase es un miembro de esa clase:animales.mamiferos.Ballena.obtenerNombre();
Java sigue este convenio en todo el API. Por ejemplo, el método System.out.println() que tanto se ha utilizado sigue esta nomenclatura. El nombre del paquete no se declara explícitamente porque java.lang.* siempre es importado implícitamente. System es el nombre de la clase perteneciente al paquete java.lang y está capitalizado. El nombre completo del método es:java.lang.System.out.println();

Cada nombre de paquete ha de ser único, para que el uso de paquetes sea realmente efectivo. Los conflictos de nombres pueden causar problemas a la hora de la ejecución en caso de duplicidad, ya que los ficheros de clases podrían saltar de uno a otro directorio. En caso de proyectos pequeños no es difícil mantener una unicidad de nombres, pero en caso de grandes proyectos; o se sigue una norma desde el comienzo del proyecto, o éste se convertirá en un auténtico caos.

No hay ninguna organización en Internet que controle esta nomenclatura, y muchas de las aplicaciones Java corren sobre Web. Hay que tener presente que muchos servidores Web incluyen applets de múltiples orígenes, con lo cual parece poco menos que imposible el evitar que alguien duplique nombres.

Javasoft ha reconocido este problema ya en una fase avanzada de su desarrollo, así que han indicado una convención para asegurar que los nombres de los paquetes sean únicos, basándose en los dominios, colocándolos al revés. Es decir, un dominio del tipo miempresa.com, debería colocar delante de todos sus paquetes el prefijo com.miempresa. Esto resolvería el problema de la nomenclatura, ya que los desarrolladores podrían controlar sus propios paquetes y, además, se generaría una estructura jerárquica de paquetes muy limpia. De hecho, el paquete Swing en la versión beta 3 del JDK 1.2 se situó bajo el árbol java.awt, lo cual sugería que las clases Swing dependían del AWT, cuando es un paquete autosuficiente y que no tiene mucho que ver con el AWT, así que Javasoft dió marcha atrás en su nomenclatura y colocó el paquete en su situación actual com.java.Swing.

Como norma y resumen de todo lo dicho, a la hora de crear un paquete hay que tener presente una serie de ideas:

La palabra clave package debe ser la primera sentencia que aparezca en el fichero, exceptuando, claro está, los espacios en blanco y comentarios

Es aconsejable que todas las clases que vayan a ser incluidas en el paquete se encuentren en el mismo directorio. Como se ha visto, esta recomendación se la puede uno saltar a la torera, pero se corre el riesgo de que aparezcan determinados problemas difíciles de resolver a la hora de compilar, en el supuesto caso de que no se hile muy fino
Ante todo, recordar que en un fichero únicamente puede existir, como máximo, una clase con el especificador de acceso public, debiendo coincidir el nombre del fichero con el nombre de la clase
Variable de Entorno CLASSPATH

El intérprete Java debe encontrar todas las clases referenciadas cuando se ejecuta una aplicación Java. Por defecto, Java busca en el árbol de instalación del Java esas librerías. En el Tutorial de Java de Sun, se indica que "los ficheros .class del paquete java.util están en un directorio llamado util de un directorio java, situado en algún lugar apuntado por CLASSPATH".
CLASSPATH es una variable de entorno que indica al sistema dónde debe buscar los ficheros .class que necesite. Sin embargo, lo que dice el Tutorial de Java de Sun, normalmente no es así, lo cual puede ocasionar confusión. Cuando se utiliza el JDK, no existe el directorio que se indica.

La no existencia se debe a que Java tiene la capacidad de buscar ficheros comprimidos que utilicen la tecnología zip. Esto redunda en un gran ahorro de espacio en disco y además, mantiene la estructura de directorios en el fichero comprimido. Por tanto, se podría parafrasear lo indicado por Sun escribiendo que "en algún lugar del disco, se encontrará un fichero comprimido (zip) que contiene una gran cantidad de ficheros .class. Antes de haber sido comprimidos, los ficheros .class del paquete java.util estaban situados en un directorio llamado util de un directorio java. Estos ficheros, junto con sus estructura se almacenar en el fichero comprimido que debe encontrarse en algún lugar apuntado por CLASSPATH".

CLASSPATH contiene la lista de directorios en los que se debe buscar los árboles jerárquicos de librerías de clases. La sintaxis de esta variable de entorno varía dependiendo del sistema operativo que se esté utilizando; en sistemas Unix, contiene una lista de directorios separados por : (dos puntos), mientras que en sistemas Windows, la lista de directorios está separada por ; (punto y coma). La sentencia siguiente muestra un ejemplo de esta variables en un sistema Unix:CLASSPATH=/home/users/afq/java/classes:/opt/apps/Java
indicando al intérprete Java que busque en los directorios /home/users/afq/java/classes y /opt/apps/Java las librerías de clases.
Paquetes de Java

El lenguaje Java proporciona una serie de paquetes que incluyen ventanas, utilidades, un sistema de entrada/salida general, herramientas y comunicaciones. En la versión actual del JDK, algunos de los paquetes Java que se incluyen son los que se muestran a continuación, que no es una lista exhaustiva, sino para que el lector pueda tener una idea aproximada de lo que contienen los paquetes más importantes que proporciona el JDK de Sun. Posteriormente, en el desarrollo de otros apartados del Tutorial, se introducirán otros paquetes que también forman parte del JDK y que, incorporan características a Java que hacen de él un lenguaje mucho más potente y versátil, como son los paquetes Java2D o Swing, que han entrado a formar parte oficial del JDK en la versión JDK 1.2.
java.applet

Este paquete contiene clases diseñadas para usar con applets. Hay la clase Applet y tres interfaces: AppletContext, AppletStub y AudioClip.
java.awt
El paquete Abstract Windowing Toolkit (awt) contiene clases para generar widgets y componentes GUI (Interfaz Gráfico de Usuario), de manipulación de imágenes, impresión, fuentes de caracteres, cursores, etc.. Incluye las clases Button, Checkbox, Choice, Component, Graphics, Menu, Panel, TextArea, TextField...
java.io
El paquete de entrada/salida contiene las clases de acceso a ficheros, de filtrado de información, serialización de objetos, etc.: FileInputStream, FileOutputStream, FileReader, FileWriter. También contiene los interfaces que facilitan la utilización de las clases: DataInput, DataOutput, Externalizable, FileFilter, FilenameFilter, ObjectInput, ObjectOutput, Serializable...
java.lang
Este paquete incluye las clases del lenguaje Java propiamente dicho: Object, Thread, Exception, System, Integer, Float, Math, String, Package, Process, Runtime, etc.
java.net
Este paquete da soporte a las conexiones del protocolo TCP/IP y, además, incluye las clases Socket, URL y URLConnection.
java.sql
Este paquete incluye todos los interfaces que dan acceso a Bases de Datos a través de JDBC, Java DataBase Connectivity, como son: Array, Blob, Connection, Driver, Ref, ResultSet, SQLData, SQLInput, SQLOutput, Statement, Struct; y algunas clases específicas: Date, DriveManager, Time, Types...
java.util
Este paquete es una miscelánea de clases útiles para muchas cosas en programación: estructuras de datos, fechas, horas, internacionalización,etc. Se incluyen, entre otras, Date (fecha), Dictionary (diccionario), List (lista), Map (mapa), Random (números aleatorios) y Stack (pila FIFO). Dentro de este paquete, hay tres paquetes muy interesantes: java.util.jar, que proporciona clases para leer y crear ficheros JAR; java.util.mime, que proporciona clases para manipular tipos MIME, Multipurpose Internet Mail Extension (RFC 2045, RFC 2046) y java.util.zip, que proporciona clases para comprimir, descomprimir, calcular checksums de datos, etc. con los formatos estándar ZIP y GZIP.

Referencias

Java se asemeja mucho a C y C++. Esta similitud, evidentemente intencionada, es la mejor herramienta para los programadores, ya que facilita en gran manera su transición a Java. Desafortunadamente, tantas similitudes hacen que no se repare en algunas diferencias que son vitales. La terminología utilizada en estos lenguajes, a veces es la misma, pero hay grandes diferencias subyacentes en su significado, como se ha recalcado a lo largo de esta sección.
C tiene tipos de datos básicos y punteros. C++ modifica un poco este panorama y le añade los tipos referencia. Java también especifica sus tipos primitivos, elimina cualquier tipo de punteros y tiene tipos referencia mucho más claros.

Todo este maremágnum de terminología provoca cierta consternación, así que los párrafos que siguen intentan aclarar lo que realmente significan los términos que se utilizan.

Se conocen ya ampliamente todos los tipos básicos de datos: datos base, integrados, primitivos e internos; que son muy semejantes en C, C++ y Java; aunque Java simplifica un poco su uso a los desarrolladores haciendo que el chequeo de tipos sea bastante más rígido. Además, Java añade los tipos boolean y hace imprescindible el uso de este tipo booleano en sentencias condicionales.
Punteros

C y C++ permiten la declaración y uso de punteros, que pueden ser utilizados en cualquier lugar. Esta tremenda flexibilidad resulta muy útil, pero también es la causa de que se pueda colgar todo el sistema.

La intención principal en el uso de los punteros es comunicarse más directamente con el hardware, haciendo que el código se acelere. Desafortunadamente, este modelo de tan bajo nivel hace que se pierda robustez y seguridad en la programación y hace muy difíciles tareas como la liberación automática de memoria, la defragmentación de memoria, o realizar programación distribuida de forma clara y eficiente.
Referencias en C++

Las referencias se incorporaron a C++ en un intento de manejar punteros de C de forma más limpia y segura. Sin embargo, como no elimina los punteros, la verdad es que su propósito lo consigue a medias. Es más, se podría decir que con las referencias C++, el lenguaje se vuelve más complicado y no es más poderoso que antes.

Las referencias deben ser inicializadas cuando se declaran y no se pueden alterar posteriormente. Esto permite incrementar la eficiencia en tiempo de ejecución sobre la solución basada en punteros, pero es más por las deficiencias de los punteros que por las ventajas de las referencias.

Referencias en Java

Las referencias en Java no son punteros ni referencias como en C++. Este hecho crea un poco de confusión entre los programadores que llegan por primera vez a Java. Las referencias en Java son identificadores de instancias de las clases Java. Una referencia dirige la atención a un objeto de un tipo específico. No hay por qué saber cómo lo hace ni se necesita saber qué hace ni, por supuesto, su implementación.

Piénsese en una referencia como si se tratase de la llave electrónica de la habitación de un hotel. Vamos a utilizar precisamente este ejemplo del Hotel para demostrar el uso y la utilización que se puede hacer de las referencias en Java. Primero se crea la clase Habitacion, que está implementada en el fichero Habitacion.java, mediante instancias de la cual se levantará el Hotel:public class Habitacion {
private int numHabitacion;
private int numCamas;

public Habitacion() {
habitacion( 0 );
}

public Habitacion( int numeroHab ) {
habitacion( numeroHab );
}

public Habitacion( int numeroHab,int camas ) {
habitacion( numeroHab );
camas( camas );
}

public synchornized int habitacion() {
return( numHabitacion );
}

public synchronized void habitacion( int numeroHab ) {
numHabitacion = numeroHab;
}

public synchronized int camas() {
return( camas );
}

public syncronized void camas( int camas ) {
numCamas = camas;
}
}
El código anterior sería el corazón de la aplicación. Ahora se construye el Hotel creando Habitaciones y asignándole a cada una de ellas su llave electrónica; tal como muestra el código siguiente,
Hotel1.java:public class Hotel1 {
public static void main( String args[] ) {
Habitacion llaveHab1; // paso 1
Habitacion llaveHab2;

llaveHab1 = new Habitacion( 222 ); // pasos 2, 3, 4 y 5
llaveHab2 = new Habitacion( 1144,3 );
// ^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^
// A B y D C
}
}
Para explicar el proceso, se dividen las acciones en los cinco pasos necesarios para poder entrar en la habitación del hotel. Aunque no se incluye, se puede también considerar el caso de la necesidad de un cerrajero, para que ante la pérdida de la llave se pueda abrir la puerta; y que, en el caso particular de este hotel virtual Java, sería el garbage collector, que recicla la habitación una vez que se hayan perdido todas las llaves.

El primer paso es la creación de la llave, es decir, definir la variable referencia, por defecto nula.
El resto de los pasos se agrupan en una sola sentencia Java. La parte B en el código anterior indica al gerente del Hotel que ya dispone de una nueva habitación. La parte C llama al decorador de interiores para que "vista" la habitación según un patrón determinado, para que no desentonen unas habitaciones con otras y no se pierdan las señas de identidad del hotel. El código electrónico que permitirá acceder a la habitación se genera en la parte D, una vez conocido el interior de la habitación y ya se programa en la llave en la parte A.

Si se abandona el ejemplo real a un lado y se detiene uno en lo que ocurre en la ejecución del código, se observa que el operador new busca espacio para una instancia de un objeto de una clase determinada e inicializa la memoria a los valores adecuados. Luego invoca al método constructor de la clase, proporcionándole los argumentos adecuados. El operador new devuelve una referencia a sí mismo, que es inmediatamente asignada a la variable referencia.

Se pueden tener múltiples llaves para una misma habitación: . . .
Habitacion llaveHab3,llaveHab4;
llaveHab3 = llaveHab1;
llaveHab4 = llavehab2;
De este modo se consiguen copias de las llaves. Las habitaciones en sí mismas no se han tocado en este proceso. Así que, ya hay dos llaves para la habitación 222 y otras dos para la habitación 1144.

Una llave puede ser programada para que funcione solamente con una habitación en cualquier momento, pero se puede cambiar su código electrónico para que funcione con alguna otra habitación; por ejemplo, para cambiar una habitación anteriormente utilizada por un empedernido fumador por otra limpia de olores y con vistas al mar. Se cambiaría la llave duplicada de la habitación del fumador (222) por la habitación con olor a sal marina, 1144: . . .
llaveHab3 = llaveHab2;

Ahora hay una llave para la habitación 222 y tres para la habitación 1144; manteniendo una llave para cada habitación en la conserjería, para poder utilizarla como llave maestra, en el caso de que alguien pierda su llave propia.

Alguien con la llave de una habitación puede hacer cambios en ella, y los compañeros que tengan llave de esa misma habitación, no tendrán conocimiento de esos cambios hasta que vuelvan a entrar en la habitación. Por ejemplo, si que quita una de las camas de la habitación, entrando en ella con la llave maestra: . . .
llaveHab2.camas( 2 );
Ahora cuando los inquilinos entren en la habitación podrán comprobar el cambio realizado: . . .
llaveHab4.printData();
Referencias y Arrays

Como en C y C++, Java dispone de arrays de tipos primitivos o de clases. Los arrays en C y C++ son básicamente un acompañante para los punteros. En Java, sin embargo, son ciudadanos de primera clase.

Para expandir el hotel creando todo un ala de habitaciones, Hotel2.java, se creará un juego de llaves maestras y luego se construirán las habitaciones:public class Hotel2 {
// Número de habitaciones por ala
public static final int habPorAla = 12;

public static void main( String args[] ) {
Habitacion llavesMaestras[]; // paso 1
llavesMaestras = new Habitacion[ habPorAla ]; // pasos 2-5
// ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// A B, C, D y E
int numPiso = 1;
for( int i=0; i < habPorAla; i++ ) // pasos 6-9
llavesMaestras[ i ] = new Habitacion( numPiso * 100 + i,
( 0 == (i%2)) ? 2 : 1 );
for( int i=0; i < habPorAla; i++ ) // pasos 10-11
llavesMaestras[i].printData();
}
}
Cada paso en el ejemplo es semejante al que ya se ha visto antes. El paso 1 especifica que el juego de llaves maestras es un grupo de llaves de habitaciones.

Los pasos 2 a 5 son, en este caso, la parte principal. En lugar de crear una habitación, el gerente ordena construir un grupo contiguo de habitaciones. El número de llaves se especifica entre corchetes y todas se crean en blanco.

Los pasos 6 a 9 son idénticos a los pasos 2 a 5 del ejemplo anterior, excepto en que en este caso todas las llaves pasan a formar parte del juego maestro. Los números de piso se dan en miles para que cuando se creen las habitaciones, todas tengan el mismo formato. También todas las habitaciones de número par tienen una sola cama, mientras que las habitaciones impares tendrán dos camas.

Los pasos 10 y 11 permiten obtener información de cada una de las habitaciones del hotel.
Referencias y Listas

Hay gente que piensa que como Java no dispone de punteros, resulta demasiado complejo construir listas enlazadas, árboles binarios y grafos. Java dispone de estructuras de datos de esos tipos y además proporciona otras muchas como: mapas, conjuntos, tablas Hash, diccionarios, etc., que en el JDK 1.2 están muy bien diseñadas y siguen la nomenclatura al uso de la OOP. No obstante, en los párrafos siguientes se demuestra que quien así piensa está bastante equivocado, porque incluso de forma pedestre se pueden construir estas estructuras de datos sin demasiado esfuerzo.

Hay que retomar el ejemplo de los arrays, y en vez de éstos utilizar una lista doblemente enlazada. El paquete de la lista simple se compone de dos clases. Cada elemento de la lista es un NodoListaEnlazada, NodoListaEnlazada.java:public class NodoListaEnlazada {
private NodoListaEnlazada siguiente;
private NodoListaEnlazada anterior;
private Object datos;
// . . .
}
Cada NodoListaEnlazada contiene una referencia a su nodo precedente en la lista y una referencia al nodo que le sigue. También contiene una referencia genérica a cualquier clase que se use para proporcionar acceso a los datos que el usuario proporcione.

La lista enlazada, ListaEnlazada.java, contiene un nodo principio-fin y un contador para el número de nodos en la lista:public class ListaEnlazada {
public NodoListaEnlazada PrincipioFin;
private int numNodos;
// . . .
}
El nodo especial PrincipioFin es sencillo, para simplificar el código. El contador se usa para optimizar los casos más habituales.

Hay que revisar pues el código del Hotel, ahora Hotel3.java, que será prácticamente el mismo que en el caso de los arrays:public class Hotel3 {
// Número de habitaciones por ala
public static final int habPorAla = 12;

public static void main( String args[] ) {
ListaEnlazada llaveMaestra; // paso 1
llaveMaestra = new ListaEnlazada(); // pasos 2-5

int numPiso = 1;
for( int i=0; i < habPorAla; i++ ) // pasos 6-9
llaveMaestra.insertAt( i,
new Habitacion( numPiso * 100 + i,
( 0 == (i%2)) ? 2 : 1 );
for( int i=0; i < habPorAla; i++ ) // pasos 10-12
( (Habitacion)llaveMaestra.getAt(i) ).printData();
}
}
El paso 1 es la llave maestra de la lista. Está representada por una lista generica; es decir, una lista de llaves que cumple la convención que se ha establecido. Se podría acelerar el tiempo de compilación metiendo la lista genérica ListaEnlazada dentro de una ListaEnlazadaHabitacion.
Los pasos 2 a 5 son equivalentes a los del primer ejemplo. Se construye e inicializa una nueva ListaEnlazada, que se usará como juego de llaves maestras.

Los pasos 6 a 9 son funcionalmente idénticos a los del ejemplo anterior con arrays, pero con diferente sintaxis. En Java, los arrays y el operador [] son internos del lenguaje. Como Java no soporta la sobrecarga de operadores por parte del usuario, solamente se puede utilizar en su forma normal.

La ListaEnlazada proporciona el método insertAt() que coge el índice en la lista, donde el nuevo nodo ha de ser insertado, como primer argumento. El segundo argumento es el objeto que será almacenado en la lista. Obsérvese que no es necesario colocar moldeo alguno para hacer algo a una clase descendiente que depende de uno de sus padres.

Los pasos 10 a 12 provocan la misma salida que los pasos 10 y 11 del ejemplo con arrays. El paso 10 coge la llave del juego que se indica en el método getAt(). En este momento, el sistema no sabe qué datos contiene la llave, porque el contenido de la habitación es genérico. Pero el programa sí sabe lo que hay en la lista, así que informa al sistema haciendo un moldeado a la llave de la habitación (este casting generará un chequeo en tiempo de ejecución por parte del compilador, para asegurarse de que se trata de una Habitacion). El paso 12 usa la llave para imprimir la información.

Punteros C/C++ y Referencias Java

Ahora que ya se conoce un poco más sobre las referencias en Java, es hora de compararlas con los punteros de C y C++.

Los punteros en C y C++ están orientados hacia un modelo físico de funcionamiento. Es decir, que el modelo de punteros se mapea directamente sobre el modelo hardware. Este modelo asume cosas como el no movimiento, lo que hace que mecanismos como su liberación automática resulten mucho menos eficientes o simplemente imposibles. Cosas como la distribución en redes y la persistencia de objetos son muy difíciles de conseguir en C y C++.

C y C++ permiten el uso de punteros de tal forma que se puede corromper el sistema, cosa que no puede suceder con las referencias en Java. Cualquier intento de hacer esto sería abortado por el compilador o por el sistema, en tiempo de ejecución (lanzando una excepción). C y C++ dejan la protección de memoria al sistema operativo, que solamente tiene el recurso de generar un error del sistema cuando un puntero accede a una posición no válida. Por el contrario, con el uso de las referencias, Java protege al programador contra sus propias tendencias autodestructivas.




No hay comentarios: