Java Ibm
Java Ibm
Objetivos
El lenguaje Java es lo suficientemente maduro y sofisticado para ayudarlo a usted a cumplir casi
cualquier tarea de programación. En este tutorial, le presentaremos funciones del lenguaje Java
que necesitará para manejar escenarios de programación compleja, que incluyen:
• Manejo de excepciones
• Herencia y abstracción
• Interfaces
• Clases anidadas
• Expresiones regulares
• Genéricos
• Tipos de enum
• E/S
• Serialización
Requisitos previos
El contenido de este tutorial está orientado a programadores nuevos en el lenguaje Java que
no conocen sus funciones más sofisticadas. El tutorial asume que usted ha trabajado en la
"Introducción a la programación Java, parte 1: Conceptos básicos del lenguaje Java" para:
• JDK 6 de Sun/Oracle.
• IDE Eclipse para desarrolladores Java.
• Un sistema que soporte JDK 6 con al menos 1 GB de memoria principal. Java 6 tiene soporte
en Linux®, Windows® y Solaris®.
• Al menos 20 MB de espacio en disco para instalar los componentes y ejemplos del software
que se cubrieron.
• Sobrecarga de métodos
• Alteración temporal de métodos
• Comparación de un objeto con otro
Sobrecarga de métodos
Cuando usted crea dos métodos con el mismo nombre pero con diferentes listas de argumentos
(es decir, diferentes números o tipos de parámetros), tiene un método sobrecargado. Los métodos
sobrecargados siempre están en la misma clase. Al tiempo de ejecución, el Java Runtime
Environment (JRE, también conocido como el tiempo de ejecución Java) decide qué variación de
su método sobrecargado llamar en base a los argumentos que se han pasado.
Suponga que Person necesita un par de métodos para imprimir una auditoría de su estado actual.
Llamaré a esos métodos printAudit(). Pegue el método sobrecargado en el Listado 1 en la vista
de edición de Eclipse:
En este caso, usted tiene dos versiones sobrecargadas de printAudit() y en realidad una usa
a la otra. Al proveer dos versiones, le da al interlocutor la elección sobre cómo imprimir una
auditoría de la clase. Dependiendo de los parámetros que se pasen, el tiempo de ejecución Java
llamará el método correcto.
Recuerde de la Parte 1 de este tutorial que Employee puede ser una subclase (o hijo) de Person
que tiene algunos atributos adicionales:
Para declarar tal clase en un archivo llamado Employee.java, haga clic derecho en el paquete
com.makotogroup.intro en Eclipse. Elija New > Class... y se abrirá el recuadro de diálogo de
Clase Java nueva, como se muestra en la Ilustración 1:
Escriba Employee como el nombre de la clase y Person como su superclase, luego haga clic en
Finish. Verá la clase Employee en una ventana de edición. No necesita explícitamente declarar
un constructor pero continúe e implemente ambos constructores de todos modos. Primero,
asegúrese de que la ventana de edición de la clase Employee tenga el foco, luego vaya a Source
> Generate Constructors from Superclass... y verá un diálogo que se parece a la Ilustración 2:
Verifique ambos constructores (como se muestra en la Ilustración 2) y haga clic en OK. Eclipse
generará los constructores por usted. Ahora debería tener una clase Employee como la del Listado
2:
public Employee() {
super();
// TODO Auto-generated constructor stub
}
import java.math.BigDecimal;
public Employee() {
super();
}
public String getTaxpayerIdentificationNumber() {
return taxpayerIdentificationNumber;
}
public void setTaxpayerIdentificationNumber(String taxpayerIdentificationNumber) {
this.taxpayerIdentificationNumber = taxpayerIdentificationNumber;
}
// Other getter/setters...
}
Para hacer esto en su IDE Eclipse, vaya a Source > Override/Implement Methods... y verá un
recuadro de diálogo que se parece a la Ilustración 3:
La llamada a super.printAudit() no necesita ser la primera; solo pareció una buena idea
imprimir primero esos atributos. De hecho, no necesita llamar para nada a super.printAudit().
Si usted no lo llama, debe formatear usted mismo los atributos desde Person (en el método
Employee.printAudit()) o excluirlos completamente. Hacer la llamada a super.printAudit(), en
este caso, es más fácil.
Miembros de clases
Las variables y los métodos que usted tiene en Person y Employee son variables y métodos
de instancia. Para usarlos, usted debe crear instancias de la clase que necesita o tener una
referencia a la instancia. Cada instancia de objetos tiene variables y métodos y, para cada uno,
el comportamiento exacto (por ejemplo, lo que se genera al llamar a printAudit()) será diferente
porque se basa en el estado de la instancia de los objetos.
Las clases mismas también pueden tener variables y métodos, que se llaman miembros de
clases. Usted declara miembros de clases con la palabra clave static introducida en la Parte 1
de este tutorial. Las diferencias entre los miembros de clases y los miembros de instancias son las
siguientes:
• Cada instancia de una clase comparte una sola copia de una variable de clase.
• Puede llamar a los métodos de clases en la clase misma, sin tener una instancia.
• Los métodos de instancias pueden acceder a las variables de clases pero los métodos de
clases no pueden acceder a variables de instancias.
• Los métodos de clases pueden acceder solo a las variables de clases.
• para declarar constantes que cualquier instancia de la clase pueda usar (y cuyo valor esté fijo
al momento del desarrollo).
• para realizar un seguimiento de "contadores" de instancias de la clase.
• en una clase con métodos de utilidad que nunca necesitan una instancia de la clase (como
Logger.getLogger()).
Variables de clases
Para crear una variable de clase, use la palabra clave static cuando la declare:
accessSpecifier static variableName [= initialValue];
Nota: Los corchetes aquí indican que sus contenidos son opcionales. No son parte de la sintaxis
de declaración.
El JRE crea espacio en la memoria para almacenar cada una de las variables de instancias de
una clase para cada instancia de esa clase. En cambio, el JRE crea sólo una copia de cada
variable de clase, a pesar de la cantidad de instancias. Lo hace de eso modo la primera vez que
se carga la clase (es decir, la primera vez que encuentra la clase en un programa). Todas las
instancias de la clase compartirán esa sola copia de la variable. Eso hace que las variables de
clases sean una buena elección para las constantes que todas las instancias deberían poder usar.
Por ejemplo, usted declaró el atributo de Género de Person para que sea una String pero no puso
ninguna restricción para ella. El Listado 4 muestra un uso común de las variables de clases:
Declaración de constantes
Normalmente, las constantes:
Métodos de clases
Si usted ha estado siguiendo lo establecido desde la Parte 1, ya ha llamado al método estático
Logger.getLogger() varias veces, — cada vez que haya recuperado una instancia de Logger
para escribir alguna salida a la consola. Observe de todos modos que usted no necesitaba una
instancia de Logger para hacer esto; en cambio, hizo referencia a la clase Logger misma. Esta
es la sintaxis para hacer una llamada de método de clase. Como con las variables de clases, la
palabra clave static identifica a Logger (en este ejemplo) como un método de clase. Los métodos
de clases también se llaman a veces métodos estáticos por este motivo.
• La instancia Logger se declara con acceso private, por lo que ninguna clase fuera de
Employee puede acceder a la referencia de forma directa.
• El Logger se inicializa cuando se carga la clase. Esto sucede porque usted usa la sintaxis del
inicializador Java para darle un valor.
Para recuperar el objeto del Logger de la clase Employee, haga la siguiente llamada:
Logger employeeLogger = Employee.getLogger();
Comparación de objetos
El lenguaje Java proporciona dos maneras para comparar objetos:
• El operador ==
• El método equals()
Comparación de objetos con ==
La sintaxis == compara objetos por la igualdad para que a == b retorne verdadero solo si a y b
tienen el mismo valor. Para los objetos, esto significa que los dos se refieren a la misma instancia
de objeto. Para los primitivos, significa que los valores son idénticos. Considere el ejemplo en el
Listado 6:
En el primer caso del Listado 6, los valores de los primitivos son los mismos, por lo tanto el
operador == retorna verdadero. En el segundo caso, los objetos Integer se refieren a la misma
instancia, por lo tanto de nuevo == retorna verdadero. En el tercer caso, aun cuando los objetos
Integer envuelven el mismo valor, == retorna falso porque integer1 e integer2 se refieren a
objetos diferentes. En base a esto, debería estar claro por qué employee1 == employee2 retorna
falso.
equals()es un método que cada objeto del lenguaje Java obtiene gratis porque se define como
un método de instancia de java.lang.Object (del que cada objeto Java hereda).
Usted llama equals() exactamente como lo haría con cualquier otro método:
a.equals(b);
Esta sentencia invoca al método equals() del objeto a, pasándole una referencia al objeto b. De
modo predeterminado, un programa Java simplemente verificaría para ver que los dos objetos
fueran los mismos al usar la sintaxis ==. Sin embargo, debido a que equals() es un método, se
puede alterar temporalmente. Considere el ejemplo del Listado 6, modificado en el Listado 7 para
comparar los dos objetos que usan equals():
Los escritores del JDK decidieron que para Integer, el significado de equals() sería diferente
del predeterminado (que es comparar las referencias del objeto para ver si se refieren al mismo
objeto) y, en cambio, retornarían verdadero en casos en los que el valor int subyacente es el
mismo.
Básicamente, esto significa que para cualquier objeto que usted escribe, puede definir lo que
equals() significa como adecuado para la aplicación que está escribiendo.
Ha usado este diálogo antes pero, en este caso, usted quiere implementar el método de
superclase Object.equals(). Así que, encuentre Object en la lista, verifique el método
equals(Object) y haga clic en OK. Eclipse generará el código correcto y lo ubicará en su archivo
de origen.
Tiene sentido que los dos objetos Employee sean iguales si los estados de esos objetos son
iguales. Es decir, son iguales si sus valores, — apellido, nombre, edad, — son iguales.
Eclipse puede generar un método equals() por usted en base a las variables de instancias
(atributos) que usted ha definido para una clase. Debido a que Employee es una subclase de
Person, usted primero generará equals() para Person. En la vista Project Explorer de Eclipse,
haga clic derecho en Person y elija Generate hashCode() and equals() para acceder al recuadro
de diálogo que se muestra en la Ilustración 5:
Seleccione todos los atributos (como se muestra en la Ilustración 5) y haga clic en OK. Eclipse
generará un método equals() que se parece al del Listado 8:
No se preocupe por el hashCode() por ahora, — puede dejarlo o suprimirlo. El método equals()
generado por Eclipse parece complicado pero lo que hace es muy simple: si el objeto que pasó
es el mismo objeto que el del Listado 8, entonces equals() retornará verdadero. Si el objeto que
pasó es nulo, retornará falso.
Luego de eso, el método verifica para ver si los objetos de la Class son los mismos (es decir que
el objeto que pasó debe ser un objeto Person). Si eso es verdad, entonces cada valor de atributo
del objeto que pasó se verifica para ver si coincide valor por valor con el estado de la instancia de
Person dada. Si los valores de los atributos son nulos (es decir, que faltan), entonces el equals()
verificará tantos como pueda y, si esos coinciden, los objetos se considerarán iguales. Tal vez no
quiera este comportamiento para cada programa pero funciona para la mayoría de los propósitos.
En este caso, una coincidencia solo en el Nombre fue suficiente para convencer a equals() de que
los dos objetos eran iguales. Siéntase libre para agregar más atributos a este ejemplo y vea lo
que obtiene.
Si sospecha que Eclipse puede generar un método toString() por usted, está en lo correcto.
Retroceda en su Project Explorer y haga clic derecho en la clase Employee, luego elija Source
> Generate toString().... Verá un recuadro de diálogo similar al de la Ilustración 5. Elija todos
los atributos y haga clic en OK. El código generado por Eclipse para Employee se muestra en el
Listado 9:
El código que Eclipse genera para toString no incluye la toString() de la superclase (al ser la
superclase de Employee una Person). Puede arreglar eso en solo un momento, usando Eclipse,
con esta alteración temporal:
@Override
public String toString() {
return super.toString() +
"Employee [employeeNumber=" + employeeNumber + ", salary=" + salary
+ ", taxpayerIdentificationNumber=" + taxpayerIdentificationNumber
+ "]";
}
toString()ahora hace el trabajo pesado de formatear el estado actual del objeto y usted
simplemente coloca lo que retorna en el StringBuilder y regresa.
Recomiendo implementar toString() siempre en sus clases, aunque sea solo por propósitos de
soporte. Es prácticamente inevitable que, en algún punto, usted quiera ver cuál es el estado de un
objeto mientras su aplicación se está ejecutando y toString() es un gran modo para hacer eso.
Excepciones
Ningún programa funciona nunca el 100 por ciento del tiempo y los diseñadores del lenguaje Java
sabían esto. En esta sección, aprenda acerca del mecanismo incorporado de la plataforma Java
para manejar situaciones donde su código no funcione exactamente como se planeó.
Observe que se ha comentado el inicializador para la variable estática que tiene la referencia de
Logger. Ejecute este código y obtendrá la siguiente salida:
Esta salida le está diciendo que usted está intentando hacer referencia a un objeto que está allí,
lo cual es un error de desarrollo bastante serio. Afortunadamente, puede usar los bloques try y
catch para obtenerlo (junto con un poco de ayuda de finally).
Juntos, los bloques try, catch y finally forman una red para obtener las excepciones. Primero,
la sentencia try recorta el código que puede arrojar una excepción. Si lo hace, la ejecución baja
inmediatamente al bloque catch o manejador de excepciones. Cuando termina los procesos de
probar y obtener, la ejecución continúa con el bloque finally, ya sea que se haya arrojado una
excepción o no. Cuando obtiene una excepción, puede intentar recuperarse con gracia de ella o
puede salir del programa (o método).
En el Listado 11, el programa se recupera del error, luego imprime un mensaje para reportar lo
que sucedió.
La jerarquía de excepciones
El lenguaje Java incorpora toda una jerarquía de excepciones que consiste en muchos tipos de
excepciones agrupados en dos categorías importantes:
Cuando un programa causa una excepción, se dice que arroja la excepción. Una excepción
verificada se declara al compilador por cualquier método con la palabra clave throws en su firma
de método. A esto le sigue una lista de excepciones separadas por comas que el método podría
arrojar potencialmente durante el curso de su ejecución. Si su código llama a un método que
especifica que arroja uno o más tipos de excepciones, debe manejarlo de algún modo o agregar
un throws a su firma de método para pasar ese tipo de excepción.
En el caso de una excepción, el tiempo de ejecución del lenguaje Java busca un manejador de
excepciones en algún lugar de la pila. Si no encuentra uno al momento en que llega al inicio de la
pila, detendrá abruptamente el programa, como vio en el Listado 10.
Usted puede tener bloques catch múltiples pero se deben estructurar en un modo particular. Si
alguna excepción es una subclase de otra excepción, entonces la clase hija se ubica delante de la
clase padre en el orden del bloque catch. Aquí hay un ejemplo:
try {
// Code here...
} catch (NullPointerException e) {
// Handle NPE...
} catch (Exception e) {
// Handle more general exception here...
}
Ha visto solo un pequeño atisbo del manejo de excepciones de Java en este tutorial. El tema
podría constituir un tutorial por sí mismo. Vea Resources para aprender más acerca del manejo de
excepciones en programas Java.
Ha estado trabajando desde la Parte 1 en el ejemplo de una aplicación de recursos humanos que
incluye Person y sus subclases Employee. Ahora verá lo que sucede cuando agrega una clase
nueva a la aplicación.
El propósito de una clase controladora (como el nombre insinúa) es "controlar" una aplicación.
Observe que este simple controlador para la aplicación de recursos humanos contiene un método
main():
package com.makotogroup.intro;
public class HumanResourcesApplication {
public static void main(String[] args) {
}
}
Cree una clase controladora en Eclipse, usando el mismo procedimiento que usó para crear
Person y Employee. Nombre la clase HumanResourcesApplication, asegurándose de seleccionar la
opción para agregar un método main() a la clase. Eclipse generará la clase por usted.
Agregue algunos códigos a su nuevo main() para que luzca del siguiente modo:
public class Person {
. . .
private final Logger log = Logger.getLogger(Person.class);
. . .
public static void main(String[] args) {
Employee e = new Employee();
e.setName("J Smith");
e.setEmployeeNumber("0001");
e.setTaxpayerIdentificationNumber("123-45-6789");
e.printAudit(log);
}
. . .
}
Ahora inicie la clase HumanResourcesApplication y obsérvela ejecutarse. Debería ver esta salida
(aquí las barras inclinadas invertidas indican una continuación de línea):
Eso es todo lo que hay realmente sobre la creación de una aplicación Java simple. En la siguiente
sección, comenzará a observar algunas de las sintaxis y bibliotecas que le ayudarán a desarrollar
aplicaciones más complejas.
Herencia
Usted se ha encontrado con ejemplos de herencia algunas veces ya en este tutorial. Esta sección
revisa algunos de los materiales de la Parte 1 sobre la herencia y explica en más detalle cómo
funciona la herencia, — incluye jerarquía de herencia, constructores y herencia y abstracción de
herencia.
Por ejemplo, suponga que usted tiene una clase Person que se parece a la del Listado 12:
// . . .
public class Person {
public static final String GENDER_MALE = "MALE";
public static final String GENDER_FEMALE = "FEMALE";
public Person() {
//Nothing to do...
}
private String name;
private int age;
private int height;
private int weight;
private String eyeColor;
private String gender;
// . . .
La clase Person en el Listado 12 hereda implícitamente del Object. Debido a que eso se asume
para cada clase, no necesita escribir extends Object para cada clase que usted define. Pero,
¿qué significa decir que una clase hereda de su superclase? Significa simplemente que Person
tiene acceso a las variables y métodos expuestos en sus superclases. En este caso, Person
puede ver y usar métodos y variables públicas de Object y métodos y variables protegidas de
Object.
Sin embargo, el lenguaje Java soporta la implementación de interfaces múltiples en una sola
clase, lo que le da un método alternativo de tipos para la herencia simple. Le presentaré las
interfaces múltiples más adelante en el tutorial.
El gráfico de herencia Employee insinúa que Employee tiene acceso a todas las variables y
métodos públicos y protegidos en Person (porque lo extiende directamente), así como también en
Object (porque en realidad extiende también esa clase, aunque indirectamente). Sin embargo,
debido a que Employee y Person están en el mismo paquete, Employee también tiene acceso a las
variables y métodos privados del paquete (a veces denominados amigables) en Person.
Para profundizar un paso más en la jerarquía de clases, podría crear una tercera clase que
extiende Employee:
public class Manager extends Employee {
// . . .
}
En el lenguaje Java, cualquier clase puede tener, como mucho, una superclase pero una clase
puede tener cualquier cantidad de subclases. Eso es lo más importante de recordar sobre la
jerarquía de herencia en el lenguaje Java.
Constructores y herencia
Los constructores no son plenos miembros orientados a objetos, por lo tanto no son heredados.
En cambio, usted debe implementarlos explícitamente en subclases. Antes de eso, revisaré
algunas reglas básicas sobre cómo se definen e invocan los constructores.
Cada clase tiene por lo menos un constructor y, si no define explícitamente un constructor para
su clase, el compilador generará uno por usted, denominado el constructor predeterminado. La
definición de la clase anterior y ésta son idénticas en el modo en que funcionan:
public class Person {
}
// Meanwhile, in Employee.java
public class Employee extends Person {
public Employee() {
super("Elmer J Fudd");
}
}
Sin embargo, probablemente usted nunca quiera inicializar un nuevo objeto Employee de este
modo. Hasta que se sienta más cómodo con los conceptos orientados a objetos, y la sintaxis Java
en general, es una buena idea implementar constructores de superclases en subclases, si piensa
que los necesitará, e invocarlos homogéneamente El Listado 14 define un constructor en Employee
que se parece al de Person para que coincidan. Es mucho menos confuso desde el punto de vista
del mantenimiento.
Declaración de un constructor
O:
public class Person {
public Person() {
}
}
// Meanwhile, in Employee.java
public class Employee extends Person {
public Employee() {
super();
}
}
Verá este modismo frecuentemente, en el que un constructor delega a otro, al pasar algún valor
predeterminado si se invoca ese constructor. También es una excelente forma de agregar un
constructor nuevo a una clase mientras se minimiza el impacto en el código que ya usa un
constructor más antiguo.
Sin modificador (privado del paquete) Cualquier clase en el mismo paquete puede invocar un constructor.
Tal vez pueda pensar en casos de uso en los que los constructores se declararían protegidos
o incluso privados del paquete pero, ¿de qué modo es un constructor privado útil? He usado
constructores privados cuando no quería permitir una creación directa de un objeto por medio de
la palabra clave new al implementar, digamos, el patrón Factory (vea Resources). En ese caso,
se usaría un método estático para crear instancias de la clase y se le permitiría a ese método, al
estar incluido en la clase misma, invocar el constructor privado:
Herencia y abstracción
Si una subclase altera temporalmente a un método de una superclase, ese método está
esencialmente oculto porque llamar a ese método por medio de una referencia a la subclase
invoca a la versión de subclase del método, no a la versión de la superclase. Esto no quiere
decir que el método de la superclase ya no sea accesible. La subclase puede invocar el método
de superclase al prologuear el nombre del método con la palabra clave super (y a diferencia de
lo que sucede con las reglas del constructor, esto se puede hacer desde cualquier línea en el
Lo mismo corresponde para las variables, siempre que el interlocutor tenga acceso a la variable
(es decir, la variable es visible al código que intenta acceder a ella). Esto puede causarle un
sinfín de dolor mientras gana competencia en la programación Java. Sin embargo, Eclipse
proporcionará varios avisos de que está ocultando una variable de una superclase o de que una
llamada de método no llamará lo que usted piensa que hará.
El poder de la abstracción
Suponga que necesita un método para examinar el estado de un objeto Employee y que se
asegure que sea válido. Esta necesidad parecería ser común a todos los objetos Employee pero
se comportaría de modo bastante diferente entre todas las subclases potenciales que no habría
potencial para la reutilización. En ese caso, se declara el método validate() como abstracto (lo
que obliga a todas las subclases a implementarlo):
public abstract class Employee extends Person {
public abstract boolean validate();
}
A cada subclase directa de Employee (tal como Manager) ahora se le requiere implementar
el método validate(). Sin embargo, una vez que una subclase ha implementado el método
validate(), ninguna de sus subclases necesita implementarlo.
Por ejemplo, suponga que tiene un objeto Executive que amplía a Manager. Esta definición sería
perfectamente válida:
public class Executive extends Manager {
public Executive() {
}
}
En segundo lugar, con lo poderosos que son, resístase al uso de las clases abstractas cuando
pueda. A menos que sus superclases contengan muchos comportamientos comunes, y por
sí solos no son realmente significativos, deje que permanezcan no abstractas. Los profundos
gráficos de herencia pueden hacer que el mantenimiento de códigos sea difícil. Considere el
equilibrio entre las clases que son muy grandes y los códigos plausibles de ser mantenidos.
Asignaciones: Clases
Cuando asigne una referencia de una clase a una variable de un tipo que pertenece a otra clase,
puede hacerlo pero hay reglas. Observemos este ejemplo:
Manager m = new Manager();
Employee e = new Employee();
Person p = m; // okay
p = e; // still okay
Employee e2 = e; // yep, okay
e = m; // still okay
e2 = p; // wrong!
Si no es así, es posible que las asignaciones de los objetos con diferentes gráficos de herencia
(tales como Manager y Employee) se asignen a una variable del tipo incorrecto. Considere este
ejemplo:
Manager m = new Manager();
Person p = m; // so far so good
Employee e = m; // okay
Employee e = p; // wrong!
Interfaces
En esta sección, comience a aprender acerca de las interfaces y comience a usarlas en su código
Java.
Una declaración de interfaz parece una declaración de clases, excepto que usted usa la palabra
clave interface. Puede nombrar la interfaz del modo que quiera (sujeto a las reglas del lenguaje)
pero, por convenio, los nombres de interfaces se parecen a los nombres de clases.
Defina las jerarquías de las interfaces, como lo hace para las clases, con la excepción de que una
sola clase puede implementar tantas interfaces como quiera. (Recuerde, una clase puede ampliar
solo una clase). Si una clase amplía a otra e implementa una interfaz o interfaces, entonces las
interfaces se enumeran luego de la clase ampliada, como a continuación:
public class Manager extends Employee implements BonusEligible, StockOptionRecipient {
// Etc...
}
Interfaces marcadoras
Una interfaz no necesita tener ningún tipo de cuerpo. De hecho, la siguiente definición es
perfectamente aceptable:
Una vez que sabe todo eso, en realidad definir una interfaz es fácil:
public interface StockOptionRecipient {
void processStockOptions(int numberOfOptions, BigDecimal price);
}
Implementación de interfaces
Para usar una interfaz, la implementa, que simplemente significa proporcionar un cuerpo de
método, que a su vez proporciona el comportamiento para cumplir el contrato de la interfaz. Eso
se hace con la palabra clave implements:
public class className extends superclassName implements interfaceName {
// Class Body
}
Una clase abstracta puede declarar que implementa una interfaz particular pero no se le requiere
que implemente todos los métodos en esa interfaz. Esto sucede porque no se requieren clases
abstractas para proporcionar implementaciones para todos los métodos que afirman implementar.
Sin embargo, la primera clase concreta (es decir, la primera que se puede crear como instancia)
debe implementar todos los métodos que la jerarquía no implementa.
Uso de interfaces
Una interfaz define un nuevo tipo de datos de referencia, lo que significa que usted puede
referirse a una interfaz en cualquier lugar en el que usted se referiría a una clase. Esto incluye
los casos en que usted declara una variable de referencia o proyecta de un tipo a otro, como se
muestra en el Listado 16.
Como puede ver, es perfectamente válido asignar una nueva instancia Manager a una referencia
StockOptionEligible, así como pasar una nueva instancia Manager a un método que espera una
referencia StockOptionEligible.
Asignaciones: Clases
Cuando asigne una referencia de una clase que implemente una interfaz a una variable de un tipo
de interfaz, puede hacerlo pero hay reglas. Desde el Listado 16, vemos que asignar una instancia
Manager a una referencia de variable StockOptionEligible es perfectamente válido. La razón
es que la clase Manager implementa esa interfaz. Sin embargo, la siguiente asignación no sería
válida:
Manager m = new Manager();
StockOptionEligible soe = m; //okay
Employee e = soe; // Wrong!
Debido a que Employee es un supertipo de Manager, al principio esto puede parecer bien, pero no
lo es. Debido a que Manager es una especialización de Employee, es *diferente* y, en este caso
particular, implementa una interfaz que Employee no implementa.
Las asignaciones como estas siguen las reglas de asignación que vimos en Inheritance. Y del
mismo modo que sucede con las clases, solo puede asignar una referencia de interfaz a una
variable del mismo tipo o a un tipo de superinterfaz.
Clases anidadas
En esta sección, aprenda acerca de las clases anidadas y dónde y cómo usarlas.
}
}
Al igual que las variables y los métodos miembro, las clases Java también se pueden definir en
cualquier ámbito, incluidos los public, private o protected. Las clases anidadas pueden ser
útiles cuando usted quiere manejar procesos internos dentro de su clase de un modo orientado a
objetos, pero esta funcionalidad está limitada a la clase en donde la necesita.
Normalmente, usted usará una clase anidada para casos en los que necesite una clase que esté
asociada firmemente con la clase en que se define. Una clase anidada tiene acceso a los datos
privados dentro de su clase cerrada pero esto conlleva algunos efectos secundarios que no son
evidentes cuando comienza a trabajar con clases anidadas (o internas).
Suponga que tiene la siguiente relación entre un Manager y una clase anidada llamada
DirectReports, que es una colección de los Employees que presentan informes a ese Manager:
Al igual que cada objeto Manager representa a un ser humano único, el objeto DirectReports
representa una colección de las personas reales (empleados) que presentan informes a un
gestor. DirectReports diferirá de un Manager a otro. En este caso, tiene sentido que uno sólo
haga referencia a la clase anidada DirectReports en el contexto de su instancia de inclusión de
Manager, por lo tanto la he hecho private.
DirectReports. En este caso, parece que usted podría darle a la clase DirectReports un ámbito
public y entonces cualquier código externo podría crear instancias de DirectReports, como se
muestra en el Listado 17:
Para crear una instancia de una clase anidada pública, se usa una versión especial del operador
new. En combinación con una referencia a alguna instancia de inclusión de una clase externa, new
le permite crear una instancia de la clase anidada:
public class Manager extends Employee {
public Manager() {
}
. . .
private class DirectReports {
. . .
}
}
// Meanwhile, in another method somewhere...
public static void main(String[] args) {
Manager manager = new Manager();
Manager.DirectReports dr = manager.new DirectReports();
}
Observe que la sintaxis exige una referencia a la instancia de inclusión, más un punto y la palabra
clave new, seguida de la clase que usted quiere crear.
ejemplo común de esto es implementar un Comparator, que se usa para comparar dos instancias
de la misma clase, normalmente con el propósito de ordenar (o clasificar) las clases:
public class Manager extends Employee {
. . .
public static class ManagerComparator implements Comparator<Manager> {
. . .
}
}
// Meanwhile, in another method somewhere...
public static void main(String[] args) {
Manager.ManagerComparator mc = new Manager.ManagerComparator();
. . .
}
En este caso, no necesita una instancia de inclusión. Las clases internas estáticas actúan
como sus homólogas clases Java regulares y realmente solo deberían usarse cuando usted
necesite asociar firmemente una clase con su definición. Claramente, en el caso de una clase de
programa de utilidad como ManagerComparator, crear una clase externa es innecesario y abarrota
potencialmente su base de códigos. Definir dichas clases como clases internas estáticas es lo que
se tiene que hacer.
soe.processStockOptions(numberOfOptions, reallyCheapPrice);
Expresiones regulares
Una expresión regular es esencialmente un patrón para describir un conjunto de cadenas que
comparten ese patrón. Si usted es un programador Perl, debería sentirse como en su hogar con
la sintaxis de patrón de expresión regular (regex) en el lenguaje Java. Sin embargo, si no está
acostumbrado a la sintaxis de expresiones regulares, puede parecer raro. En esta sección, usted
comienza con el uso de expresiones regulares en sus programas Java.
• Una cadena
• Una cadena más larga
• Una cadena mucho más larga
Observe que cada una de estas cadenas comienza con a y termina con string. La API de
expresiones regulares Java (vea Resources) lo ayuda a sacar estos elementos, ver el patrón entre
ellos y realizar cosas importantes con la información que ha averiguado.
La API de expresiones regulares tiene tres clases principales que usará casi todo el tiempo:
. Cualquier carácter
Las primeras pocas construcciones se llaman cuantificadoras porque cuantifican lo que está antes
que ellas. Las construcciones como \d son clases de caracteres predefinidas. Cualquier carácter
que no tenga un significado especial en un patrón es un literal y coincide consigo mismo.
Coincidencia de patrón
Con la sintaxis de patrón de la Tabla 2, puede ocuparse del simple ejemplo del Listado 19, con el
uso de clases en la API de expresiones regulares Java:
En primer lugar, el Listado 19 crea una clase Pattern al llamar a compile(), que es un método
estático en Pattern, con una serie literal que representa el patrón que usted quiere hacer coincidir.
Ese literal usa la sintaxis de patrón regex. En este ejemplo, la traducción al español del patrón es
la siguiente:
Encuentre una cadena de la forma a seguida por cero caracteres o más, seguidos por la cadena.
Cada cadena del lenguaje Java es una colección indexada de caracteres, que comienza desde
0 y termina con la longitud de la cadena menos uno. El Matcher analiza la cadena, que comienza
desde 0, y busca coincidencias con ella. Luego de que se completa ese proceso, el Matcher
• matches() le dice si toda la secuencia de entrada fue una coincidencia exacta para el patrón.
• start() le dice el valor de índice en la cadena donde comienza la cadena coincidente.
• end() le dice el valor de índice en la cadena donde termina la cadena coincidente, más uno.
El Listado 19 encuentra una sola coincidencia al comenzar en 0 y terminar en 7. De este modo, la
llamada a matches() retorna true, la llamada a start() retorna 0 y la llamada a end() retorna 8.
Podría buscarla para a.*string y obtener una coincidencia si usa lookingAt(). Pero si usa
matches(), retornaría false porque la cadena conlleva más que solo lo que está en el patrón.
Un wiki, como seguramente sabe, es un sistema basado en la web que permite a los usuarios
modificar páginas. Los wikis se basan casi por completo en expresiones regulares. Su contenido
se basa en las entradas de cadenas de los usuarios, lo cual se analiza y formatea con el uso de
expresiones regulares. Cualquier usuario puede crear un enlace a otro tema en un wiki al ingresar
una palabra wiki, que normalmente es una serie de palabras concatenadas, cada una de las
cuales comienza con una letra mayúscula, como la siguiente:
MyWikiWord
Podría buscar palabras wikis en esta cadena con un patrón regex como el siguiente:
[A-Z][a-z]*([A-Z][a-z]*)+
Ejecute este código y debería ver las tres palabras wikis en su consola.
Sustitución de cadenas
Buscar coincidencias es útil pero también puede manipular cadenas una vez que encuentra una
coincidencia para ellas. Puede hace eso al sustituir cadenas coincidentes con algo más, del
mismo modo en que podría buscar algún texto en un programa procesador de textos y sustituirlo
con otro texto. Matcher tiene un par de métodos para sustituir elementos de cadenas:
Este código encuentra palabras wikis, como antes. Cuando el Matcher encuentra una
coincidencia, sustituye el texto de la palabra wiki con su sustitución. Cuando usted ejecuta este
código, debería ver lo siguiente en su consola:
Antes: Aquí hay una WikiWord seguida de AnotherWikiWord y luego SomeWikiWord.
Después: Aquí hay una sustitución seguida de sustitución y luego sustitución.
En cada patrón, usted normalmente crea grupos al encerrar partes del patrón en paréntesis.
Los grupos se enumeran de izquierda a derecha, comenzando desde 1 (el grupo 0 representa
toda la coincidencia). El código en el Listado 20 sustituye cada palabra wiki con una cadena que
"envuelve" la palabra:
Usted podría cumplir el mismo objetivo de sustitución al usar algunos otros métodos. En lugar de
llamar a replaceAll(), podría hacer lo siguiente:
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(buffer, "blah$0blah");
}
matcher.appendTail(buffer);
Logger.getAnonymousLogger().info("After: " + buffer.toString());
Genéricos
La introducción de los genéricos en JDK 5 marcó un gran avance para el lenguaje Java. Si usted
ha usado plantillas C++, descubrirá que los genéricos en el lenguaje Java son similares, pero
no exactamente iguales. Si no ha usado plantillas C++, entonces no se preocupe: Esta sección
ofrece una introducción de alto nivel a los genéricos en el lenguaje Java.
Generics es un mecanismo compilador por el cual usted puede crear (y usar) tipos de elementos
(tales como clases e interfaces) de un modo genérico al cosechar el código común y parametrizar
(o hacer plantillas) el resto.
Genéricos en funcionamiento
Para ver la diferencia que hacen los genéricos, considere el ejemplo de una clase que ha estado
en el JDK por mucho tiempo: java.util.ArrayList, que es una Lista de objetos que está
respaldada por una matriz.
Como puede ver, la ArrayList es heterogénea: contiene dos tipos de String y un tipo de Integer.
Antes de JDK 5, no había nada en el lenguaje Java para restringir este comportamiento, lo cual
causó muchos errores de codificación. En el Listado 21, por ejemplo, todo parece estar bien hasta
el momento. Pero, ¿qué tal si se accede a los elementos de la ArrayList, lo que el Listado 22
intenta hacer?
Sin conocimiento previo de lo que está en la ArrayList, se debe verificar el elemento al que
quiere acceder para ver si puede manejar su tipo o enfrentar una posible ClassCastException.
Con los genéricos, usted puede especificar el tipo de elemento que se puso en la ArrayList. El
Listado 23 muestra cómo:
Esta sintaxis funciona para cualquier tipo de objeto que sea Iterable (es decir, implementa la
interfaz Iterable).
Clases parametrizadas
Las clases parametrizadas realmente brillan cuando se trata de colecciones, por lo tanto así
es como usted las observará. Considere la interfaz de List (real). Representa una colección
ordenada de objetos. En el caso de uso más común, se agregan elementos a la List y luego se
accede a aquellos elementos ya sea por índice o por iterar por la List.
Si está pensando en parametrizar una clase, considere si aplican los siguientes criterios:
• Una clase principal se encuentra en el centro de algún tipo de derivador: es decir, la "cosa"
en el centro de la clase puede aplicarse ampliamente y las funciones (por ejemplo, los
atributos) que la rodean son idénticas.
• Comportamiento común: usted realiza prácticamente las mismas operaciones sin tener en
cuenta la "cosa" en el centro de la clase.
Al aplicar estos dos criterios, es bastante evidente que una colección concuerde con la cuenta:
Por lo tanto, para crear una ArrayList de, digamos, java.lang.Integer, usted haría lo siguiente:
List<Integer> listOfIntegers = new ArrayList<Integer>();
SimpleList puede parametrizarse con cualquier subclase de Object. Para crear y usar una
SimpleList de, digamos, objetos java.math.BigDecimal, usted haría lo siguiente:
public static void main(String[] args) {
SimpleList<BigDecimal> sl = new SimpleList<BigDecimal>();
sl.add(BigDecimal.ONE);
log.info("SimpleList size is : " + sl.size());
sl.add(BigDecimal.ZERO);
log.info("SimpleList size is : " + sl.size());
sl.clear();
log.info("SimpleList size is : " + sl.size());
}
Tipos de enumeración
En JDK 5, un nuevo tipo de datos se agregó al lenguaje Java, denominado enum. No se lo debe
confundir con java.util.Enumeration, enum representa un conjunto de objetos constantes que
están todos relacionados con un concepto particular, cada uno de los cuales representa un valor
constante diferente en ese conjunto. Antes de que se introdujera enum al lenguaje Java, usted
habría definido un conjunto de valores constantes para un concepto (por ejemplo, gender) del
siguiente modo:
public class Person {
public static final String MALE = "male";
public static final String FEMALE = "female";
}
Cualquier código que se necesite para hacer referencia a ese valor constante se habría escrito
más o menos del siguiente modo:
public void myMethod() {
//. . .
String genderMale = Person.MALE;
//. . .
}
Usar el tipo enum hace que definir constantes sea mucho más formal y también más poderoso.
Aquí está la definición de enum para Gender:
public enum Gender {
MALE,
FEMALE
}
Eso es solo el comienzo de lo que puede hacer con enums. De hecho, las enums son muy
parecidas a las clases, por lo tanto pueden tener constructores, atributos y métodos:
package com.makotogroup.intro;
Una diferencia entre una clase y una enum es que el constructor de una enum debe declararse
private y no puede extender (o heredar de) otras enums. Sin embargo, una enum puede
implementar una interfaz.
package com.makotogroup.intro;
public interface Displayable {
public String getDisplayName();
}
Su enum Gender podría implementar esta interfaz (así como también cualquier otra enum que se
necesite para producir un nombre de pantalla amigable), como del siguiente modo:
package com.makotogroup.intro;
E/S
Esta sección es una visión general del paquete java.io. Aprenderá a usar algunas de sus
herramientas para recolectar y manipular datos de una variedad de orígenes.
Archivos
De todos los orígenes de datos disponibles para sus aplicaciones Java, los archivos son los más
comunes y, con frecuencia, los más convenientes. Si usted quiere leer un archivo en su aplicación
Java, debe usar streams que analicen sus bytes entrantes en los tipos de lenguaje Java.
java.io.File es una clase que define un recurso en su sistema de archivos y representa ese
recurso en un modo abstracto. Crear un objeto File es fácil:
File f = new File("temp.txt");
File f2 = new File("/home/steve/testFile.txt");
El constructor File toma el nombre del archivo que creará. La primera llamada crea un archivo
llamado temp.txt en el directorio dado. La segunda llamada crea un archivo en una ubicación
específica en mi sistema Linux. Usted puede pasar cualquier String al constructor del File,
siempre y cuando sea un nombre de archivo válido para su OS, ya sea que el archivo al que hace
referencia incluso exista o no.
java.io.File tiene otros métodos útiles que puede usar para suprimir archivos, crear directorios
(al pasar el nombre de un directorio como el argumento al constructor del File), determinar si un
recurso es un archivo, directorio o enlace simbólico, y más.
La acción real de E/S de Java está en la escritura a y lectura de los orígenes de datos, que es
donde entran las secuencias.
Secuencias de caracteres
Las secuencias de caracteres leen (Reader y sus subclases) y escriben (Writer y sus subclases)
caracteres de 16 bits. Aquí hay un listado seleccionado de secuencias de caracteres y sus usos:
Apenas he rozado la superficie de lo que es posible con esta biblioteca Java esencial. Por su
cuenta, intente aplicar lo que ha aprendido sobre los archivos a otros orígenes de datos.
Serialización Java
La serialización Java es otra de las bibliotecas esenciales de la plataforma Java. La serialización
se usa principalmente para la persistencia de objetos y objetos remotos, dos casos de uso
donde necesita poder tomar una instantánea del estado de un objeto y luego reconstituirlo
posteriormente. Esta sección le da una idea de la API de serialización Java y le muestra cómo
usarlo en sus programas.
java.io.Serializable
El primer paso para hacer que la serialización funcione es permitir que sus objetos usen el
mecanismo. Cada objeto que usted quiera que sea serializable debe implementar una interfaz
llamada java.io.Serializable:
import java.io.Serializable;
public class Person implements Serializable {
// etc...
}
La interfaz Serializable marca los objetos de la clase Person al tiempo de ejecución como
serializable. Cada subclase de Person también se marcará como serializable.
Cualquier atributo de un objeto que no sea serializable causará que el tiempo de ejecución Java
arroje una NotSerializableException si intenta serializar su objeto. Puede gestionar esto al usar
la palabra clave transient para comunicarle al tiempo de ejecución que no intente serializar
ciertos atributos. En ese caso, usted es responsable de asegurarse de que los atributos se
restauren para que su objeto funcione adecuadamente.
Serializar un objeto
Ahora probará un ejemplo que combina lo que acaba de aprender acerca de E/S de Java con lo
que está aprendiendo ahora acerca de la serialización.
Suponga que usted crea y llena un objeto Manager (recuerde que Manager está en el gráfico
de herencias de Person, que es serializable) y luego quiere serializar ese objeto para una
OutputStream, es este caso para un archivo. Ese proceso se muestra en el Listado 29:
m.setGender(Gender.FEMALE);
m.setAge(29);
m.setHeight(170);
m.setName("Mary D. Boss");
m.setTaxpayerIdentificationNumber("123-45-6789");
log.info("About to write object using serialization... object looks like:");
m.printAudit(log);
try {
String filename = "Manager-" + m.hashCode() + ".ser";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename));
oos.writeObject(m);
log.info("Wrote object...");
} catch (Exception e) {
log.log(Level.SEVERE, "Caught Exception processing object", e);
}
El primer paso es crear el objeto y establecer algunos valores de atributos. Luego, se crea una
OutputStream, en este caso una FileOutputStream y luego se llama a writeObject() en esa
secuencia. writeObject() es un método que usa serialización Java para serializar un objeto para
la secuencia.
En este ejemplo, usted está almacenando el objeto en un archivo pero se usa esta misma técnica
para cualquier tipo de serialización.
Deserialización de un objeto
Todo el punto de la serialización de un objeto es poder reconstituirlo, o deserializarlo. El Listado
30 lee el archivo que usted acaba de serializar y deserializa sus contenidos, restaurando de este
modo el estado del objeto Manager:
Para la mayoría de los propósitos de aplicaciones, marcar sus objetos como serializables es
de todo lo que tendrá que preocupar siempre cuando se trate de la serialización. En casos donde
usted sí necesita serializar y deserializar sus objetos explícitamente, puede usar la técnica que se
muestra en los Listados 29 y 30. Pero mientras los objetos de sus aplicaciones de desarrollan, y
usted les agrega y elimina atributos, la serialización toma una nueva capa de complejidad.
serialVersionUID
En los primeros días del middleware y la comunicación de objetos remotos, los desarrolladores
eran en gran medida responsables de controlar el "formato de conexión física" de sus objetos, lo
cual causó un sinfín de dolores de cabeza mientras la tecnología comenzaba a evolucionar.
Suponga que usted agregó un atributo a un objeto, lo recompiló y redistribuyó el código a cada
máquina en un clúster de aplicaciones. El objeto se almacenaría en una máquina con una
versión del código de serialización pero accederían otras máquinas que podrían tener una
versión diferente del código. Cuando esas máquinas intentaran deserializar el objeto, a menudo
sucederían cosas malas.
La serialización Java usa una propiedad denominada serialVersionUID para ayudarlo a lidiar
con diferentes versiones de objetos en un escenario de serialización. No necesita declarar esta
propiedad en sus objetos; de forma predeterminada, la plataforma Java usa un algoritmo que
computa un valor para ella en base a los atributos de su clase, su nombre de clase y posición en
el clúster galáctico local. La mayoría de las veces, eso funciona bien. Pero si agrega o elimina un
atributo, ese valor dinámicamente generado cambiará y el tiempo de ejecución Java arrojará una
InvalidClassException.
Para evitar esto, usted debería acostumbrase a declarar explícitamente una serialVersionUID:
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 20100515;
// etc...
}
Recomiendo usar algún tipo de plan para su número de versión de serialVersionUID (he usado la
fecha actual en el ejemplo anterior) y debería declararlo private static final y de tipo long.
Tal vez se esté preguntando cuándo cambiar esta propiedad. La respuesta breve es que debería
cambiarla cuando usted hace un cambio incompatible a la clase, lo que normalmente significa que
ha eliminado un atributo. Si usted tiene una versión del objeto en una máquina en la que se ha
eliminado el atributo, y el objeto se hace remoto en una máquina con una versión del objeto donde
se espera el atributo, entonces las cosas se pueden tornar extrañas.
Como regla general, siempre que agregue o elimine funciones (es decir, atributos o métodos) de
una clase, debería cambiar su serialVersionUID. Es mejor obtener una InvalidClassException
en el otro extremo de la conexión que un error de aplicación que se debe a un cambio de clase
incompatible.
Mientras continúa aprendiendo acerca del lenguaje y la plataforma Java, probablemente quiera
estudiar en más detalle temas como expresiones regulares, genéricos y serialización Java.
Con el tiempo, quizás también quiera explorar temas que no se cubrieron en este tutorial
introductorio, tales como la concurrencia y la persistencia. Otro tema que merece explorarse es
Java 7, que traerá muchos cambios potencialmente innovadores a la plataforma Java. Vea los
Recursos para algunos buenos puntos de partida para aprender más acerca de los conceptos
de programación Java, incluidos aquellos que son muy avanzados para que se exploren en este
formato introductorio.
Recursos
Aprender
• Java technology homepage: El sitio oficial de Java tiene enlaces a todo lo relacionado con la
plataforma Java, incluida la especificación del lenguaje Java y documentación Java API.
• Java 6: Aprenda más sobre JDK 6 y las herramientas que incluye.
• Javadoc homepage: Aprenda los detalles del uso de Javadoc, incluido cómo usar la
herramienta de línea de comandos y cómo escribir sus propios Doclets que le permiten crear
formatos personalizados para su documentación.
• New to Java technology: Verifique este compendio de recursos de developerWorks para
desarrolladores Java novatos.
• 5 things you didn't know about ...: Esta serie de developerWorks proporciona introducciones
breves a consejos y tradiciones de programación Java menos conocidos (pero a menudo
introductorios).
• "Try to catch me: Does exception handling impair performance?" (Tony Sintes, JavaWorld,
julio del 2001): Este artículo proporciona información introductoria acerca del manejo de
excepciones en programas Java.
• "Exception: Don't get thrown for a loss" (Tony Sintes, JavaWorld, febrero del 2002): Entienda
la diferencia entre las excepciones verificadas y las de tiempo de ejecución.
• "Regular expressions simplify pattern-matching code" (Jeff Friesen, JavaWorld.com, febrero
del 2003): Explore esta introducción extendida a regex.
• Refactorización: Improving the Design of Existing Code (Martin Fowler et al., Addison-
Wesley, 1999): Este libro es un recurso excelente para aprender cómo escribir un código
más limpio y más plausible de mantener.
• Patrones de diseño: Elements of reusable object-oriented software (Erich Gammaet al.,
Addison-Wesley, 1994): Aprenda más acerca del patrón Factory, uno de los 23 patrones de
diseño que continúan definiendo el desarrollo de software contemporáneo.
• The Java Tutorials: Obtenga una introducción detallada al lenguaje Java.
• La developerWorks Java technology zone: Cientos de artículos acerca de cada aspecto de la
programación Java.
Comentar
Sobre el autor
J. Steven Perry