1 Declaracion de Clases e Interfaces
1 Declaracion de Clases e Interfaces
1 Declaracion de Clases e Interfaces
DECLARACIÓN DE CLASES E
INTERFACES
SEMANA 7
[ HERRAMIENTAS PARA LA
DECLARACIÓN DE CLASES E INTERFACES
Programación orientada a objetos, Clases y Objetos
En programación orientada a objetos, los sistemas pueden describirse como una comunidad
de agentes que interactúan entre si por medio de mensajes, asumiendo responsabilidades y
respondiendo a requerimientos de otros agentes del sistema. Cada agente, dentro de un
diseño orientado a objetos, será reconocido como un objeto, y cada objeto tiene un papel
determinado gracias a que goza de un comportamiento y un estado. Es fácil reconocer los
comportamientos que pueden presentar un objeto, y el estado que puede exhibir en un
momento determinado, gracias a que cada objeto es de un tipo específico definido por una
clase o interfaz.
Clase. Una clase podría pensarse como una plantilla que describe los comportamientos
que un objeto de esta clase puede soportar y los atributos que definirán el estado del
objeto de la clase (por ejemplo: color, peso, valor, etc.)
Objeto. Con una clase no se puede interactuar, pues estas juegan el papel de planos,
indicando cómo deben estar constituidos los objetos de esta clase. No se puede volar a
Europa en el plano de un avión, se necesita un avión concreto, un objeto de la clase
avión para poder llevar a cabo este requerimiento. Pasar de la clase al objeto se conoce
como instanciación, y el objeto se convierte en una instancia de la clase que lo define.
Cada objeto tiene su propio estado, y puede presentar todos los comportamientos
definidos en su clase.
Estado (atributos, variables de instancia). Cada objeto (instancia de una clase), contiene
un conjunto único de variables de instancia definidas en su clase. El valor asignado a
estos atributos o variables de instancia, definen el estado de este objeto en un
momento dado.
Comportamiento (métodos). Si un objeto puede llegar a presentar un comportamiento
dado, es porque él sabe la forma como ejecutar la(s) tarea(s) que debe llevar a cabo
para demostrar este comportamiento. Por ejemplo, un avión puede volar porque está
diseñado para responder a los requerimientos de los pilotos, ejecutando una serie de
tareas mecánicas que le permiten, finalmente, despegar. Cada clase define métodos con
los cuales las instancias de dicha clase podrán responder a los requerimientos de otros
objetos y presentar los comportamientos esperados. Los métodos son algoritmos, en
donde se define la lógica de las clases.
Empecemos conociendo las reglas que usa el compilador para reconocer un identificador
legal.
Los identificadores deben comenzar con una letra, un signo pesos, o un guion bajo
“underscore” (_). El primer carácter de un identificador no puede ser un número.
Después del primer carácter, un identificador puede estar compuesto de cualquier
combinación de letras, números, signos pesos y guiones bajos (_).
Los identificadores son sensibles a mayúsculas, “var” y “Var” son identificadores
diferentes.
No hay límite en el número de caracteres que un identificador puede contener.
No se puede usar como identificador ninguna de las palabras reservadas de Java
(keywords), las cuales se listan en la siguiente tabla.
[ HERRAMIENTAS PARA LA 3
PRODUCTIVIDAD ]
Según las anteriores reglas, son ejemplos de identificadores legales:
int ___id;
float $variable;
boolean $$$$$$_$$$$$;
long $150000;
char un_identificador_valido_aunque_un_poco_extenso_para_mi_gusto;
int _;
short _1;
float 2_variable;
long $100.000;
double valor#3;
char 23;
long The_Thorin's_Hall;
boolean –test;
Las reglas para identificadores legales no solo aplican para nombres de variables, también
son validas para los nombres de todos los componentes de Java, como clases, métodos e
interfaces; por esto, la comunidad de programadores hace uso de convenciones, que aunque
no representan reglas para el compilador, incrementan la legibilidad y compatibilidad del
código fuente al diferenciar claramente qué tipo de componente se está utilizando. Ejemplo
de estas convenciones son el uso de una letra mayúscula como primer carácter del
identificador de una clase o interface, o separar las palabras en un identificador compuesto
por más de una, por medio del formato “camelCase”, es decir, la primera letra de cada
palabra compuesta debe ir en mayúscula como enEsteEjemplo.
Solo puede haber una clase pública por archivo de código fuente. Si esta clase pública se
define, el nombre de esta clase debe ser el mismo del archivo de código fuente que la
contiene.
Pueden aparecer comentarios al inicio o final de cualquier línea de código del archivo
fuente, al no ser tenidos en cuenta por el compilador, estos representan una excepción
a las reglas de posicionamiento que se enuncian aquí.
Si la clase hace parte de un paquete, la declaración de pertenencia a este paquete debe
ser la primera línea de código del archivo.
Si se necesita importar uno o más paquetes o clases individuales de estos paquetes, las
sentencias de importe import, deben ser ubicadas entre la declaración de paquete y la
declaración de la clase. En caso de que la clase no pertenezca a ningún paquete en
particular, las sentencias import deben ser las primeras en el archivo de código fuente.
Un archivo de código fuente puede tener más de una clase no pública. Solo puede haber
una clase pública, pero pueden haber más clases además de esta, siempre y cuando no
tengan modificador de acceso público.
Un archivo de código fuente puede no tener clases públicas, en este caso no es
necesario que el nombre de ninguna de estas coincida con el nombre del archivo que las
contiene.
Las sentencias package e import hacen un uso especial de los nombres completos de las
clases (fully qualified name).
[ HERRAMIENTAS PARA LA 5
PRODUCTIVIDAD ]
referenciarlas usando su nombre completo al momento de usarlas. Por ejemplo, en la
siguiente clase:
java.util.Vector<Integer> numerous;
numeros.add(aleatorios.nextInt(51));
El uso de los nombres completos de las clases Vector y Random hace innecesario el uso de las
sentencias import para estas clases, aunque es preferible escribirlas y de esta forma
ahorrarse trabajo al escribir los nombres completos de las clases, que usualmente son
bastante largos. A partir de Java 5 se ha incorporado una nueva característica relacionada
con las sentencias import, que pretende ahorrar aún más la codificación necesaria para
trabajar con clases de paquetes externos, esta vez pensando en el uso de miembros
estáticos de estas clases. Esta característica se conoce como importaciones estáticas (statics
imports), y permite importar los miembros estáticos de una clase para ser utilizados
directamente en el código, sin necesidad de referenciar las clases que los definen. Veámoslo
más claro con un ejemplo:
String in;
if(numero == aleatorio.nextInt(5)+1)
System.out.println("Adivinó");
else
System.out.println("No adivinó");
import java.util.Random;
import javax.swing.JOptionPane;
String in;
[ HERRAMIENTAS PARA LA 7
PRODUCTIVIDAD ]
Random aleatorio = new Random();
if(numero == aleatorio.nextInt(5)+1)
System.out.println("Adivinó");
else
System.out.println("No adivinó");
import java.util.Random;
String in;
if(numero == aleatorio.nextInt(5)+1)
out.println("Adivinó");
else
Los tres ejemplos presentan el mismo comportamiento, la única diferencia está en el uso de
las clases de paquetes externos.
La sintaxis para usar un importe estático debe ser como se observa: import static
seguido por el nombre completo (fully qualified name) del miembro estático que se
desee importar, como en el ejemplo el método parseInt de la clase Integer, o el atributo
out de la clase System.
Si se necesita importar todos los miembros estáticos de una clase, se puede usar un
asterisco en cambio del nombre del miembro particular, como en el ejemplo al importar
todos los miembros estáticos de la clase JOptionPane.
Hay que usar con cuidado esta característica, pues afecta la legibilidad del código, ya que al
utilizar los miembros estáticos directamente, pueden llegar a confundirse con los miembros
propios de la clase que se está definiendo.
Importar miembros estáticos de esta forma puede llevar a referencias ambiguas, por ejemplo
si se importan todos los miembros estáticos de la clase java.lang.Integer y también se
importan todos los miembros estáticos de la clase java.lang.Long, se presentan
ambigüedades como en el caso de la constante MAX_VALUE, en este caso no habría forma de
saber a cuál de los dos miembros se referencia al llamarlos directamente dentro del código,
en casos como este el compilador producirá un error.
Modificadores
Al momento de declarar atributos, métodos o clases (incluyendo clases internas que se
tratarán a fondo en otra lectura), se pueden utilizar diversos modificadores que permiten
controlar a fondo el comportamiento y papel del componente que se está definiendo. Los
modificadores pueden catalogarse en dos tipos según si son modificadores de acceso o no.
[ HERRAMIENTAS PARA LA 9
PRODUCTIVIDAD ]
Modificadores de acceso
Al trabajar con los modificadores de acceso se debe tener especial cuidado ya que dadas las
características de estos en Java, pueden ser utilizados para probar la atención del aspirante
en el examen de certificación. Esto se debe a que existen cuatro niveles de acceso, pero solo
tres modificadores. Si no se antepone un modificador de acceso a una declaración, (de
método, atributo o clase) se podría pensar en que simplemente no se quiere controlar el
acceso a ese componente, pero en realidad se está asignando un nivel de control acceso y no
es el menos restrictivo. De esta forma, siempre, cualquier método, clase o atributo de clase
declarado tiene un nivel de acceso controlado, sea explícitamente asignado o no.
En general, cuando desde el código de una clase se desea acceder a otra clase, es porque se
necesita realizar alguna de las siguientes acciones:
En la declaración de una clase (excluyendo las clases internas ya que son conceptos
distintos) pueden aplicarse dos tipos de modificadores de acceso.
Una clase con nivel de acceso por defecto no tiene un modificador de acceso explicito en su
declaración, por tanto, este es el nivel de acceso que se obtiene cuando no se especifica
ningún modificador al declarar una clase. Al nivel de control de acceso por defecto también
se le conoce como acceso a nivel de paquete, dado que la clase declarada con este nivel de
control, puede ser vista únicamente por clases que se encuentren en el mismo paquete. Si
una clase de un paquete distinto intenta acceder a una clase con nivel de acceso por defecto
el compilador desplegara un error. Es decir, fuera del paquete en que se declara, esta clase
no podrá ser instanciada o heredada; para cualquier clase externa al paquete, se puede
asumir que la clase ni siquiera existe.
Una clase que en su declaración utilice el modificador de acceso public provee acceso a todas
las clases en todos los paquetes del diseño de la aplicación. Una clase pública puede ser
instanciada o heredada desde cualquier otra clase, siempre y cuando no existan otros
modificadores como los que analizaremos a continuación.
Otros modificadores
Otros modificadores que se pueden aplicar a la declaración de una clase son: final, abstract, y
strictfp. Estos modificadores, al no estar orientados a restringir el nivel de acceso, pueden ser
usados conjuntamente con los modificadores de acceso descritos anteriormente, aunque no
siempre se pueden mezclar entre ellos. Veamos entonces cuál es la funcionalidad y
comportamiento de estos modificadores que no son de acceso.
Strictfp
Final
Al marcar una clase con el modificador final se impide que esta clase sea extendida, es decir,
que no se puede usar como clase padre en una relación de herencia. Al marcar una clase con
este modificador se está indicando que con esta se llegó al nivel final de herencia, esta clase
no se puede extender más, y sus métodos no podrán ser sobrescritos por una versión más
especializada. En el api de Java hay varios ejemplos de este tipo de clases, uno de ellos: la
clase String, la cual está marcada como final en su declaración, por tanto, es un intento de
extenderla, como en el siguiente ejemplo:
[ HERRAMIENTAS PARA LA 11
PRODUCTIVIDAD ]
Resultaría en un error del compilador indicando que la clase String no puede ser heredada ya
que se marcó como final en su declaración.
Abstract
Si una clase tiene al menos un método declarado como abstracto (los detalles de esto los
estudiaremos más adelante), la clase debe ser marcada como abstracta lo que impedirá que
se creen instancias de esta clase. Un método abstracto no tiene implementación, es decir no
tiene definido un conjunto de instrucciones que le permitan responder a un llamado. Si una
clase tiene al menos un método de este tipo, ¿qué pasaría si desde un objeto de esta clase se
hiciera un llamado al método abstracto?, ¿de qué forma debería responder la aplicación, si no
tiene definido un conjunto de instrucciones para responder al llamado? Por esto, una clase
marcada como abstracta no puede ser instanciada, su finalidad entonces se reduce a ser
clase padre en una línea de herencia en donde finalmente serán implementados sus métodos
abstractos.
//otros atributos
//otros métodos
Todo compila perfectamente, aún cuando el método finaliza con un punto y coma y no con
los corchetes que delimitan su implementación, esto gracias a que el método se declaró con
el modificador abstract, claro está. Pero al intentar compilar el siguiente código en alguna
otra clase:
Más adelante detallaremos más aspectos de los métodos abstractos, por ahora nos importa
recalcar que:
Las clases abstractas son de gran utilidad a la hora de brindar comportamiento polimórfico a
los objetos, en colaboración con las interfaces, estas clases brindan una gran capacidad de
extensibilidad y flexibilidad a las implementaciones de los diseños orientados a objetos.
Declaración de interfaces
Es posible relacionar objetos de diferentes líneas de herencia a partir de un comportamiento
compartido y de esta forma agruparlos como si todos pertenecieran a un mismo tipo. Por
ejemplo, si se tiene la clase Murciélago que extiende de Mamífero, y la clase Avión que
extiende de Vehículo, ¿cómo agrupar los objetos de estas clases según su único aspecto en
común? No tienen relación alguna de herencia, entonces no se podrían agrupar como
objetos de algún supertipo, como cuando agrupamos peras y manzanas como objetos de la
clase Fruta. Es aquí donde tienen utilidad las interfaces, los aviones y los murciélagos solo
tienen una característica en común, ambos pueden responder al llamado de volar, ambos
pueden volar y, por tanto, son Voladores. Una interfaz, al ser implementada puede
determinar qué es lo que una clase puede hacer, pero no es obligación de ella definir como lo
debe hacer. Podemos entonces, para el ejemplo, crear una interfaz Volador, e indicar que los
objetos de las clases que implementen esta interfaz tienen que ser capaces de volar, mas no
[ HERRAMIENTAS PARA LA 13
PRODUCTIVIDAD ]
define cómo lo deben hacer, esto es responsabilidad de la clase Avión y Murciélago. Al tener
una interfaz Volador, ahora es posible agrupar objetos de diferentes líneas de herencia como
si pertenecieran a un tipo que los define a todos, en este caso el de los objetos que pueden
volar. Si se desea incluir a otro grupo de objetos en esta categoría, la clase de estos objetos
debe implementar la interfaz Volador, y está en la obligación de implementar los métodos
que esta interfaz tenga definidos, entre los cuales debe estar el método volar.
Una interfaz es entonces como una clase totalmente abstracta, por tanto, solo puede definir
en su interior métodos abstractos, que le indican a las clases que las implementan los
comportamientos que deben poder ejecutar. No pueden haber métodos concretos dentro
de una interfaz, por tanto, tampoco se pueden instanciar interfaces. La forma como se
pueden definir los métodos en una interfaz es bastante reducida, tanto que incluso aún si el
programador no lo define explícitamente, el compilador asume siempre un comportamiento
que se basa en las siguientes reglas:
Todos los métodos de una interfaz son implícitamente públicos y abstractos. Esto
significa que aún cuando no se utilicen los modificadores public y abstract en la
declaración del método, este igualmente seguirá siendo público y abstracto.
Todas las variables de instancia (atributos) definidos dentro de una interfaz deben se
públicas, estáticas y finales. Esto será de esta forma aun cuando al declarar no se
incluyan los modificadores public, static y final. Significa esto que las interfaces solo
pueden definir constantes como atributos, no variables de instancia en el sentido
estricto de la palabra.
Los métodos de una interfaz no pueden ser marcados con el modificador static.
Al ser abstractos los métodos de una interfaz no pueden ser marcados con los
modificadores final, strictfp o native. Más adelante detallaremos estos modificadores a
nivel de métodos.
A diferencia de las clases, una interfaz puede heredar de una o más interfaces. (Cuidado,
la herencia entre interfaces es solo entre interfaces, una interfaz no puede heredar de
una clase).
Una interfaz no puede implementar otra interfaz (Las interfaces solo contienen
métodos abstractos, por tanto, no podría implementar los métodos de otra interfaz).
Al declarar una interfaz se debe usar la palabra clave interface en lugar del conocido
class usado en la declaración de clases.
Las interfaces representan otra forma para lograr el polimorfismo.
En cuanto a los modificadores a la hora de trabajar con interfaces, se debe dedicar especial
atención y conocer a fondo el funcionamiento de una interfaz y sus miembros. Por ejemplo,
¿sería legal la siguiente declaración de una interfaz?
Es legal, innecesario y redundante pero legal. Un interfaz tiene todos sus métodos abstractos,
por tanto, es obvio que esta sea también abstracta, así que implícitamente todas las
interfaces son abstractas, exista o no el modificador abstract en su definición, lo que hace de
su uso algo opcional y redundante en caso de existir.
En cuanto a los métodos sucede lo mismo, y hay que estar atentos a posibles preguntas con
truco, por ejemplo:
La declaración del método volar, de nuevo es redundante pero legal. Los modificadores
public y abstract no son necesarios en este punto ya que todos los métodos en una interfaz
son implícitamente públicos y abstractos, por tanto, la anterior declaración es equivalente a:
void volar();
[ HERRAMIENTAS PARA LA 15
PRODUCTIVIDAD ]
La declaración del método volar no define ningún modificador de acceso, pero cuidado, no
significa en este caso que el método tenga un nivel de acceso por defecto, sino público ya
que implícitamente todos los métodos dentro de una interfaz lo son.
Es muy importante recordar este aspecto, ya que podrá haber entonces muchas
declaraciones distintas que finalmente sean idénticas funcionalmente, gracias a las
declaraciones implícitas de sus modificadores. Por ejemplo, cualquiera de estas cinco
declaraciones podrían incluirse independientemente para el método volar dentro de la
interfaz Volador, todas con idéntica funcionalidad:
void volar();
Como habíamos dicho anteriormente los atributos de una interfaz son siempre constantes,
garantizando de esta forma que cualquier clase que implemente la interfaz va a tener
invariablemente acceso al mismo valor.
Hay que tener de nuevo especial cuidado con los modificadores implícitos de los atributos
declarados en una interfaz, ya que estos siempre serán:
Esto implica que el uso explícito de estos modificadores no sea necesario al declarar
atributos en una interfaz. Siempre los atributos declarados en una interfaz son públicos
estáticos y finales aún cuando no se especifiquen los modificadores en la declaración.
De nuevo podemos ver el concepto en los siguientes ejemplos, todas las siguientes
instrucciones individualmente son idénticas funcionalmente para declarar un constante
dentro de una interfaz:
int valor = 1;
[ HERRAMIENTAS PARA LA 17
PRODUCTIVIDAD ]
Cualquier combinación de los modificadores requeridos es legal, teniendo en cuenta que
aunque requeridos, también son implícitos, así que pueden obviarse y declarar una constante
sin modificador alguno pero que sigue siendo publica estática y final.
Modificadores de acceso
Al trabajar con métodos y atributos, se dispone de más niveles de control de acceso que al
declarar una clase. A este nivel, además de los ya trabajados, tenemos en total los siguientes
modificadores:
public
protected
default
prívate
Recordando que default no es un modificador explícito, sino que representa el nivel de
acceso otorgado en este caso a un método o atributo declarado sin utilizar un modificador
de acceso.
En cuanto a modificadores de acceso, las restricciones aplican de la misma forma tanto para
atributos como para métodos, por tanto, nos referiremos a estos de forma conjunta como
miembros de la clase, para poder describir más fácilmente el comportamiento de los
modificadores.
Cuando desde una clase se desea acceder a un miembro de otra clase, debe analizarse la
visibilidad que este miembro declara, pues dependiendo del ámbito de la llamada, puede que
esté restringido el acceso. Un primer aspecto a analizar es el nivel de acceso de la clase, que
incide directamente en el nivel de acceso de sus miembros, por ejemplo si la clase declara un
nivel de acceso por defecto, no importa que sus miembros sean públicos, solo serán visibles
en el ámbito del paquete en donde fue declarada su clase contenedora.
Cabe anotar que los miembros no estáticos de una clase pueden ser accedidos por otra clase
de dos formas distintas:
Miembros públicos
Cuando un miembro de una clase (sea método o atributo), se declara como público, significa
que cualquier clase, dentro o fuera del paquete de su clase, puede acceder a él, asumiendo
que el modificador de la clase así lo permita.
A través de una instancia de la clase, desde algún método de cualquier clase de cualquier
paquete (si se tiene una instancia es porque la clase también tiene visibilidad), se puede
acceder a los miembros que tengan acceso público sin problemas. Lo mismo sucede cuando
se hereda de la clase, la subclase hereda y, por tanto, tiene acceso a todos los miembros
públicos declarados en la superclase.
Miembros privados
Un miembro declarado como privado, no puede ser accedido desde ninguna otra clase
distinta a la clase en que fue declarado. Significa entonces que el miembro, sea método o
atributo solo existe para servir internamente en la clase en donde se declaró, para el resto de
clases es invisible, no existe. Si la clase que declara miembros privados es extendida, incluso
su subclase no podrá acceder a los miembros privados, es decir los miembros privados no se
heredan. Es importante siempre tener en cuenta que una subclase no puede acceder
directamente a los miembros privados de su superclase.
En una subclase, es posible declarar un método con la misma firma que la de un método
privado de la superclase, pero esto en ningún caso es sobreescritura de métodos (overriding),
pues lo que realmente se está declarando es un nuevo método, que coincide con el nombre,
parámetros y retorno del método privado de su superclase, pero que en ningún momento lo
está redefiniendo, porque para la subclase el método privado no existe. Las reglas de
sobreescritura de métodos no aplican para este caso, los métodos privados no pueden ser
sobrescritos.
El nivel de protección de acceso por defecto (default), es el que se asigna a un miembro que
no declare explícitamente un modificador de acceso. Por ejemplo:
[ HERRAMIENTAS PARA LA 19
PRODUCTIVIDAD ]
package paquete;
int atributo;
void método() {
El acceso por defecto solo permite acceder a los miembros de una clase desde clases que se
encuentren en el mismo paquete que la clase que los define. Para el resto de clases (aquellas
de paquetes externos) los miembros con nivel de acceso por defecto no existen. Si
intentáramos usar el método definido en la clase anterior en un código como el siguiente:
package otroPaquete;
import paquete;
prueba.método();
El nivel de acceso por defecto también se le conoce como acceso a nivel de paquete.
Miembros protegidos
package paquete;
Si se quiere acceder a los miembros de la clase Prueba, esto sólo sería posible si el acceso se
efectúa desde una clase en el mismo paquete, o que esté relacionada en línea de herencia
con Prueba. Por ejemplo:
[ HERRAMIENTAS PARA LA 21
PRODUCTIVIDAD ]
package otroPaquete;
import paquete;
prueba.método();
Cuidado, el método es protected, y se está intentando acceder a él desde una clase en línea
de herencia pero en paquetes distintos. En principio, según las reglas establecidas, parece
que no habría problema, excepto que el acceso al método se hace a través de una referencia
a la clase y no por medio de la herencia, esto hace que el método no sea visible pues el
llamado se encuentra en un paquete diferente. Al tratar de compilar el anterior ejemplo,
resultaría en un error de compilación. Si se quisiera acceder al método protegido en el
ámbito del ejemplo tendría que hacerse por medio de la herencia, modifiquemos la clase
para acceder al método de forma legal:
package otroPaquete;
import paquete;
this.metodo();
El llamado this.metodo() en el método test de la clase PruebaHija seria ahora también ilegal,
pues el nivel de acceso por defecto impide la visibilidad del método en clases que estén por
fuera del paquete en el que se declaró.
Otros modificadores
Ya hemos analizado el efecto de aplicar modificadores de acceso a los miembros de una clase,
pero de nuevo estos no son los únicos modificadores que se pueden utilizar. Veamos qué
otros modificadores, que no son de acceso, se pueden usar al momento de declarar un
miembro de una clase (método o atributo).
Ya trabajamos con los modificadores final y abstract, los cuales también se pueden aplicar en
algunos miembros, pero además también se dispone de native, transient, synchronized, static
y el también anteriormente mencionado strictfp.
En este punto es necesario tratar a los métodos y atributos de forma separada, ya que aplicar
los modificadores sobre ellos tendrá efectos distintos, o en algunos casos no podrán
aplicarse dependiendo si el miembro es un método o un atributo.
Un método que sea marcado como final, no puede sobrescribirse en el caso de que la clase
que lo declare sea extendida. Es decir, su implementación no puede cambiarse, se utiliza
cuando es necesario proteger la funcionalidad del método. Declarar un método final no
implica que la clase que lo contiene también tenga que ser final, la clase se puede seguir
extendiendo, solo que los métodos marcados como final no podrán sobrescribirse.
Parámetros finales
Las variables locales declaradas dentro de un método no pueden ser marcadas con
modificadores de ningún tipo excepto el modificador final el cual determinaría que la variable
se comportará como una constante y su valor nunca podrá cambiar una vez asignado. Los
parámetros de un método también se comportan internamente como variables locales, y el
modificador final también puede aplicarse sobre ellos, como en el siguiente ejemplo:
[ HERRAMIENTAS PARA LA 23
PRODUCTIVIDAD ]
public boolean verificaEdad ( int edad, final int edadMinima) { }
Al marcar el parámetro edadMinima como final, se evita que su valor sea modificado durante
la ejecución del método, el parámetro siempre mantendrá el valor del argumento pasado en
el momento de la llamada al método.
Métodos abstractos
Hay que recordar que una clase que tenga al menos un método abstracto debe declararse
también abstracta.
La clase Caballo no compila, pues tiene un método abstracto y no se declaró como abstracta.
¿Cuál método abstracto? Aún cuando la clase está vacía, está heredando de la clase Mamifero
lo cual tiene como consecuencia que el método abstracto también se hereda, y mientras este
no se redefina seguirá siendo abstracto. La clase Murcielago, por otra parte, tampoco
implementa el método abstracto de la clase Mamifero, pero está en cambio se declara como
abstracta, por esto el código compila perfectamente.
Un método jamás puede marcarse como final y abstract simultáneamente; no tendría sentido,
pues el modificador final impide que el método sea redefinido, pero el modificador abstract
exige que el método sea sobreescrito para brindarle una implementación, luego utilizarlos
conjuntamente no tiene sentido y el compilador detecta un intento como este como un error.
Tampoco tiene sentido marcar un método como abstract y private simultáneamente, pues el
modificador private impide que el método sea heredado y, por tanto, sobrescrito para
brindar una implementación, lo que no tiene sentido al tratarse de un método abstracto.
[ HERRAMIENTAS PARA LA 25
PRODUCTIVIDAD ]
Métodos sincronizados (synchronized)
Los métodos sincronizados protegen los datos del acceso concurrente en caso de que
múltiples hilos intenten acceder simultáneamente al método. Al ser sincronizado, un método
solo permite que un hilo lo ejecute a la vez. Para sincronizar un método se usa en su
declaración el modificador synchronized. Por ejemplo:
El modificador synchronized puede ser usado en forma conjunta con cualquiera de los
modificadores de acceso.
Métodos nativos
Un método marcado como nativo indica que el método ha sido implementado con código
dependiente de la plataforma por lo general escrito en C. No es necesario para los objetivos
del examen conocer cómo se usa este tipo de métodos. Solo basta conocer que para
declarar un método nativo se debe usar la palabra clave native como modificador. El
modificador native solo puede ser usado en la declaración de un método, no en la de una
clase o atributo. Los métodos nativos, así como los abstractos, no proveen una
implementación, por tanto, en vez de cuerpo su declaración finaliza con un punto y coma.
[ HERRAMIENTAS PARA LA 27
PRODUCTIVIDAD ]
Solo se puede declarar un “var-arg” por método.
Cuando se declara un “varg-arg” este debe ocupar el último lugar en la lista de parámetros.
Declaración de variables
Al declarar una variable, su tipo puede asociarse con dos grandes grupos:
Tipos primitivos: Son ocho en Java, para valores numéricos enteros: int, long, short, byte
y dentro de estos como representación de los caracteres char; para valores numéricos
con coma flotante: float y double; y para valores booleanos boolean. Datos de este tipo
no pueden considerarse objetos, y constituyen la base de la implementación de un
diseño orientado a objetos en Java.
Tipos referenciados o por referencia: Una variable de referencia es usada para
referenciar a un objeto. Se declara según la clase del objeto que se desea referenciar.
Declaración de arreglos
Un arreglo en Java es un objeto que guarda múltiples variables de un mismo tipo o de varios
tipos relacionados por línea de herencia. Esto indica que pueden declararse ya sea de tipos
primitivos o de referencias a objetos. Para el examen es importante conocer cómo se
Como se sabe, para declarar un arreglo se debe especificar primero su tipo, seguido de un
par de corchetes ([ ]) que pueden ir antes o después del identificador del arreglo.
String [] arreglo2;
La diferencia entre ambas formas se hace visible cuando se declara varios arreglos en una
sola instrucción de declaración:
Solo var es un arreglo, var2, var3, var4 son variables simples de tipo entero.
Cuidado, según lo anteriormente enunciado la siguiente también sería una declaración valida
de una matriz:
[ HERRAMIENTAS PARA LA 29
PRODUCTIVIDAD ]
double [] matriz [];
float arregloMultiMulti[][][][][][];
Declaración de atributos
Los atributos o variables de instancia, se definen dentro del cuerpo de una clase, pero por
fuera de cualquier método, puesto que son propiedades de la clase y de su valor depende el
estado de un objeto de la clase. Usualmente se habla de “atributos”, pero los términos
“campos”, “propiedades” o “variables de instancia” son en esencia igualmente validos,
aunque en determinadas situaciones pueden adoptar diferentes significados, que no son de
importancia para el examen. Para el examen, es importante conocer acerca de los atributos:
Se pueden declarar con cualquiera de los tres modificadores de acceso, o sin ninguno de
ellos para proveer un nivel de control de acceso por defecto.
Pueden ser marcados con el modificador final o transient.
No pueden ser marcados como abstract, synchronized, strictfp o native.
Se pueden marcar con el modificador static aunque en este caso dejan de ser atributos o
variables de instancia para convertirse en variables de clase.
Atributos finales
Al declarar un atributo con el modificador final, hace que en el atributo no pueda modificarse
su valor una vez asignado explícitamente (no por defecto). Es fácil de comprender cuándo se
habla de valores primitivos, pues una vez asignado un valor a un atributo final entero, por
ejemplo 30, ese atributo siempre tendrá el valor 30 asignado y nunca podrá ser cambiado.
Con tipos referencia requiere un poco más de análisis. Estos tipos de datos almacenan
referencias a objetos, así que este valor es el que nunca puede reasignarse, la referencia al
objeto, pero esto no indica que no se puedan modificar los valores de los atributos del objeto
que se está referenciando. Los datos del objeto pueden ser modificados, pero la referencia
no puede ser reasignada. Según esto, no existen objetos finales, solo existen referencias
finales.
Al serializar un objeto, sus atributos (si también son de clases serializables o primitivos), se
transforman para poder ser enviados por un flujo de datos, por ejemplo para guardarlo en un
archivo. Algunos de estos atributos puede que no se desee que hagan parte de la
serialización del objeto, en este caso se marca con el modificador transient aquellos atributos
que se quieren ignorar en el momento de serializar el objeto.
Atributos volátiles
Así como los métodos sincronizados evitan los problemas comunes del acceso concurrente
al trabajar con hilos, los atributos volátiles implementan su propia estrategia para proteger
los datos en este tipo de situación. Al igual que con transient, volatile es un modificador que
se puede aplicar solo a atributos.
Declaración de métodos
Declaración de atributos
Clases internas a otra clase, pero no internas a un método
Bloques de inicialización.
Y que no puede usarse en:
Constructores
Clases, (las clases internas son otro concepto)
Interfaces
Clases internas a métodos
Métodos y atributos de clases internas
Variables locales.
[ HERRAMIENTAS PARA LA 31
PRODUCTIVIDAD ]