Programación orientada a objetos
Introducción a la POO
Con Java, la noción de objeto es omnipresente y precisa un mínimo de aprendizaje. En primer lugar, nos centraremos en los principios de la programación orientada a objetos y en su vocabulario asociado, para pasar luego a ponerlo todo en práctica con Java.
En un lenguaje procedural clásico, el funcionamiento de una aplicación está regido por una sucesión de llamadas a diferentes procedimientos y funciones disponibles en el código. Estos procedimientos y funciones son los encargados de procesar los datos de la aplicación representados por las variables de esta última. No existe relación alguna entre los datos y el código que los manipula. Por el contrario, en un lenguaje orientado a objetos, lo que se intenta es agrupar el código. A esta agrupación se le llama clase. Por lo tanto, una aplicación desarrollada con un lenguaje orientado a objetos está formada por numerosas clases que representan los diferentes elementos procesados por la aplicación. Las clases describirán las características de cada uno de los elementos. El ensamblado de estos elementos va a permitir el funcionamiento de la aplicación.
Este principio se utiliza ampliamente en otras disciplinas, además de la informática. En la industria automovilística, por ejemplo, seguramente...
Puesta en práctica de la POO con Java
1. Contexto
En el resto del capítulo, vamos a trabajar con la clase Person y sus subclases. Esta clase Person representará a una persona en el sentido común del término en español. Tenga en cuenta que generalmente se recomienda nombrar tus clases en inglés, y respetaremos esta norma. A continuación, se muestra una representación simplificada en UML (Unified Modeling Language) de esta jerarquía de clases.

UML es un lenguaje gráfico destinado a la representación de los conceptos de programación orientada a objetos. Para obtener más información sobre este lenguaje, puede consultar el manual UML 2.5 en la colección Recursos Informáticos de Ediciones ENI.
En UML, una flecha triangular vacía describe una derivación. La clase derivada es la clase base, pero con propiedades adicionales o modificadas, como el concepto de herencia presentado anteriormente.
Tenemos la clase Person, que hereda de clase Object (como cualquier clase en Java). Esta clase Person tendrá dos clases hijas: Customer (un cliente) y Provider (un proveedor).
2. Creación de una clase
La creación de una clase consiste en su declaración y la de todos los elementos que la componen.
a. Declaración de la clase
La declaración de una clase se lleva a cabo utilizando la palabra clave class seguida del nombre de la clase y de un bloque de código delimitado por los caracteres { y } (llaves). En este bloque de código se encuentran las declaraciones de variables, que serán los campos de la clase, y las funciones, que serán los métodos de la clase. Se pueden añadir varias palabras clave para modificar las características de la clase. Por lo tanto, la sintaxis de la declaración de una clase es la siguiente.
[modificadores] class NombreDeLaClase
[extends NombreDeLaClaseBasica]
[implements NombreDeInterfaz1,NombreDeInterfaz2,...]
{
Código de la clase
}
Los signos [ y ] (corchetes) se utilizan para indicar qué elemento es opcional. No se deben utilizar en el código de declaración de una clase....
Los comentarios
1. Presentación
Los comentarios son una parte esencial de un código bien escrito. Ayudan a explicar cómo funciona el código a los desarrolladores que lo leen, incluido tu yo futuro. Hay dos corrientes de pensamiento cuando se trata de comentar en código: aquellos que piensan que deben ser extensos y los que dicen que si tiene que añadir comentarios es porque su código está mal escrito. Probablemente esté en algún punto intermedio: los comentarios se deberían usar para explicar aspectos del código que no son evidentes, pero un código bien estructurado y legible a menudo puede prescindir de muchos comentarios.
Java ofrece varios tipos de comentarios que se pueden usar para diferentes propósitos. En este capítulo, veremos los principales tipos de comentarios disponibles en un programa Java.
2. Comentarios en una línea
Los comentarios en una línea comienzan con // y continúan hasta el final de la línea. Se utilizan para añadir explicaciones sencillas o temporales al código. Aquí tiene un ejemplo:
int number = 5; // Se declara una variable con nombre y se inicializa a 5
Este tipo de comentario es útil para añadir explicaciones concisas sobre una línea de código específica.
3. Comentarios en varias líneas
Los comentarios en varias líneas empiezan por /* y terminan...
Los paquetes
1. Presentación
La meta principal de los paquetes es organizar y ordenar las clases y las interfaces de una aplicación. El principio es el mismo que en la vida corriente. Si disponemos de un gran armario en casa, es obvio que la instalación de estanterías en este armario facilitará la organización y la búsqueda de los objetos guardados en relación con un almacenamiento en desorden.
La organización de las clases en paquetes conlleva las ventajas siguientes:
-
Facilidad para encontrar y utilizar de nuevo un elemento.
-
Limitación de los riesgos de conflictos de nombres.
-
Creación de una nueva visibilidad (la visibilidad package) además de las estándares (private, protected, public). Cuando un elemento (una clase, un campo, un método) no tiene modificadores de visibilidad, entonces este elemento solo es visible por los elementos presentes en el mismo paquete. Hay una sutileza para los elementos protegidos (campos, métodos). Por supuesto, son accesibles en las clases derivadas, pero también en las clases del mismo paquete.
2. Creación de un paquete
Lo primero que hay que hacer cuando se quiere crear un paquete es darle un nombre. Hay que elegirlo con cuidado para permitir que el paquete cumpla totalmente con su papel. Debe ser único y representativo de los elementos almacenados en su interior.
Para asegurar la unicidad del nombre de un paquete, por convención se utiliza el nombre de dominio de la empresa invirtiendo el orden de los elementos como primera parte para el nombre del paquete.
Por ejemplo, si el nombre del dominio es eni.es, la primera parte del nombre del paquete será es.eni. Si el nombre del dominio contiene caracteres prohibidos para los nombres de paquetes, se sustituyen por el carácter _ (guion bajo). De este modo, para el nombre de dominio eni-escuela.es, la primera parte del nombre del paquete será es.eni_escuela. Esta primera parte del nombre permite garantizar la unicidad de los elementos respecto al exterior de la empresa. La parte siguiente del nombre del paquete asegurará la unicidad de los elementos...
Los módulos
1. Instalación
Al crear un proyecto de Java con Eclipse, aparece la siguiente ventana para solicitar la creación de un archivo llamado module-info.java:

Este paso está vinculado a la aparición de la noción de módulo con Java 9. Haga clic en Create y se crea un archivo module-info.java en el directorio raíz, que contiene las fuentes.
En intelIiJ, la creación del archivo module-info.java solo se propone después de crear un módulo: haga clic con el botón derecho del ratón en la raíz del proyecto y seleccione New y Module.
Su contenido es el siguiente:
module Proyecto_JavaSE_Cap3_Modulos {
}
2. Presentación
Desde Java 9, el API de Java se ha modularizado. El objetivo es múltiple. La principal ventaja de la modularización del API es la ganancia en el rendimiento durante la ejecución del programa. De hecho, la modularización permite que la máquina virtual cargue solo los módulos necesarios para el correcto funcionamiento de los programas que ejecuta.
Esto puede parecer superfluo con la capacidad de cálculo de las máquinas actuales, lo que tiene todo el sentido si miramos el Internet de las cosas (IOT - Internet Of Things), es decir, los programas incorporados en los objetos habituales y cotidianos.
Otros intereses que se pueden identificar son:
-
Una plataforma modular es más...
La gestión de los errores
¡La vida de un desarrollador no es del todo fácil! Los errores son una de las fuentes principales de estrés. De hecho, si nos fijamos bien, podemos clasificar estos errores que nos arruinan la vida en tres categorías. Veremos cada una de ellas, así como las soluciones existentes para resolverlos.
1. Los diferentes tipos de error
a. Los errores de sintaxis
Este tipo de errores se produce en tiempo de compilación cuando el desarrollador comete un error tipográfico. Son habituales con las herramientas de desarrollo, en las cuales el editor de código y el compilador son dos entidades separadas, y menos frecuentes en entornos de desarrollo integrado (Intellij, Eclipse, etc.). La mayoría de estos entornos proporcionan un análisis sintáctico al mismo tiempo que se escribe el código.
Si se detecta un error de sintaxis, el entorno propone soluciones que permiten corregir este error.

Además, las «faltas de ortografía» en los nombres de campos o métodos se eliminan fácilmente mediante las funcionalidades disponibles en estos entornos.
b. Los errores de ejecución
Estos errores aparecen después de la compilación, cuando lanzamos la ejecución de la aplicación. La sintaxis del código es correcta, pero el entorno de la aplicación no permite ejecutar alguna instrucción empleada en la aplicación. Esto sucede, por ejemplo, cuando intenta abrir un archivo que no existe en el disco de su máquina. Sin duda, obtendrá un mensaje de este tipo.

Este tipo de mensajes puede confundir al usuario y restar valor a la experiencia global. Afortunadamente, Java ofrece mecanismos para detectar estos errores, evitando así la visualización de mensajes contundentes o crípticos. Estos mecanismos se detallarán más adelante en este capítulo (véase la sección La gestión de los errores).
c. Los errores de lógica
Son los peores enemigos de los desarrolladores. Todo se compila sin problema, todo se ejecuta sin errores, y sin embargo ¡¡¡no funciona como estaba previsto!!!
En este caso, hay que revisar la lógica de funcionamiento de la aplicación. Las herramientas de depuración permiten seguir el desarrollo de la aplicación paso a paso...
Los genéricos
1. Presentación
Los tipos genéricos son elementos de un programa que se adaptan automáticamente para lograr la misma funcionalidad sobre diferentes tipos de datos. Cuando crea un elemento genérico, no necesita diseñar una versión diferente para cada tipo de datos con los que desea lograr la funcionalidad. Para hacer una analogía con un objeto actual, tomaremos el ejemplo de un destornillador. Dependiendo del tipo de tornillo que tenga que usar, puede tomar un destornillador específico para este tipo de tornillo (plano, en cruz, etc.). Una técnica utilizada frecuentemente por un técnico experimentado es adquirir un destornillador universal con múltiples puntas. Dependiendo del tipo de tornillo, elige la que mejor se adapte. El resultado final es el mismo que si tuviera una variedad de destornilladores diferentes: puede atornillar y desatornillar.
Cuando usa un tipo genérico, lo configura con un tipo de datos. Esto permite que el código se adapte automáticamente y realice la misma acción independientemente del tipo de datos. Una alternativa podría ser el uso del tipo universal Object. El uso de tipos genéricos tiene varias ventajas respecto a esta solución:
-
Requiere la verificación de los tipos de datos en el momento de la compilación y evita las verificaciones inevitables que deben hacerse manualmente con el uso del tipo Object.
-
Evita las operaciones de conversión del tipo Object a un tipo más específico y viceversa.
-
La escritura del código se facilita en ciertos entornos de desarrollo, con la visualización automática de todos los miembros disponibles para un tipo particular de datos.
-
Promueve la escritura de algoritmos que son independientes de los tipos de datos.
Sin embargo, los tipos genéricos pueden imponer ciertas restricciones sobre el tipo de datos utilizados. Por ejemplo, pueden obligar a que el tipo utilizado implemente una o más interfaces o derive de una clase particular. Es importante comprender algunos términos utilizados con los genéricos.
-
Tipo genérico: esta es la definición de una clase, interfaz o función para la que se especifica al menos un tipo de datos durante su declaración.
-
Tipo de parámetro: este es el espacio reservado para el tipo...
Las colecciones
1. Presentación
Las aplicaciones necesitan, con frecuencia, manipular una gran cantidad de información. Existen numerosas estructuras que facilitan la gestión de dicha información. Se agrupan bajo el término «colección». Como ocurre en la vida, existen distintos tipos de colecciones y de coleccionadores. Puede haber personas que lo guardan todo pero que no tienen una organización particular para clasificarlo; otras que se especializan en la colección de ciertos tipos de objetos, o incluso los maníacos que toman todas las precauciones posibles para poder encontrar rápidamente un objeto…
El lenguaje Java proporciona distintas clases que permiten implementar varios modelos de gestión.
La primera solución para gestionar un conjunto de elementos consiste en utilizar tablas. Esta solución se ha descrito en la sección Los arrays del capítulo Entender un programa. Si bien son sencillas de implementar, esta solución no es demasiado flexible. Su principal defecto es el carácter fijo del tamaño de la tabla. Si no hay espacio para almacenar elementos suplementarios, hay que crear una nueva tabla, más grande, y transferirle el contenido de la tabla anterior. Esta solución, muy pesada de implementar, consume además muchos recursos.
El lenguaje Java proporciona una vasta paleta de interfaces y de clases que permiten gestionar fácilmente conjuntos de elementos. Las interfaces describen las funcionalidades disponibles, mientras que las clases implementan y proveen realmente dichas funcionalidades. En función del modelo de gestión que se desee tener sobre los elementos, se utilizará la clase mejor adaptada. No obstante, las mismas funcionalidades básicas deben estar accesibles sea cual sea el modelo de gestión. Para asegurar la presencia de estas funcionalidades, indispensables en todas las clases, se han agrupado en la interfaz Collection, que se utiliza como interfaz básica.
La jerarquía de interfaces se presenta en el siguiente diagrama.

Todas estas interfaces son genéricas, con el objetivo de gestionar conjuntos compuestos por no importa qué tipos de elementos, evitando así las molestas operaciones de cambio de tipo.
-
La interfaz Collection es la interfaz básica que proporciona...
Las entradas/salidas (I/O)
1. Introducción
Las entradas/salidas son una parte esencial de la informática, permitiendo a los programas comunicarse con el usuario, leer y escribir datos en dispositivos de almacenamiento, transmitir datos a través de una red y más. En Java, las entradas/salidas se gestiona mediante flujos (stream en inglés), que permiten leer y escribir datos secuencialmente.
En este capítulo, exploraremos los fundamentos de las entradas/salidas en Java. Primero, definiremos las entradas/salidas y explicaremos su importancia. A continuación, veremos cómo funcionan los flujos y cómo usarlos para leer y escribir datos de diferentes fuentes, como archivos y consolas. También veremos cómo serializar y deserializar objetos en Java, lo que permite almacenar y transmitir objetos de forma eficiente.
Los problemas de entrada/salida en un programa no son específicos de la POO y se pueden encontrar en contextos de programación procedural. Sin embargo, es esencial haber visto las explicaciones previas de la POO para entender mejor los ejemplos que siguen.
Definición de las entradas/salidas
Las entradas/salidas (E/S o I/O en inglés por input/output) es el proceso por el cual un programa informático interactúa con su entorno. Las entradas se refieren a los datos que el programa recibe del exterior, mientras que las salidas se refieren a los datos que el programa envía externamente. Las entradas/salidas puede incluir periféricos como teclados, monitores, impresoras, discos duros, bases de datos, redes y más.
2. Flujos de entrada y salida en Java
Los flujos son objetos que permiten gestionar el envío y la recepción de datos de forma secuencial. En Java, los flujos se clasifican en varias categorías, incluyendo flujos de procesamiento de caracteres y flujos de procesamiento de bytes.
Los flujos de procesamiento de caracteres se utilizan para leer y escribir datos de texto, mientras que los flujos de procesamiento de bytes se emplean para leer y escribir datos binarios (o bytes en inglés).
Además de los flujos, Java también define clases que se utilizan para procesar los datos del flujo. Estas clases se consideran filtros y deben estar asociadas a un flujo de lectura o escritura. Por ejemplo, hay filtros que permiten poner los datos procesados en un búfer...
Ejercicios
1. Ejercicio 1
Crear una clase que represente un artículo de una tienda de venta por correspondencia. Un artículo se caracteriza por su referencia, su nombre y su precio. Crear, a continuación, un método main que permita probar el correcto funcionamiento de la clase anterior.
2. Ejercicio 2
Agregar las dos clases Book y BluRay que heredan de la clase Article.
Un libro posee un número ISBN, contiene cierto número de páginas y lo ha escrito un autor; un Blu-Ray tiene cierta duración y lo ha producido un realizador.
Agregar los atributos necesarios a las clases Book y BluRay para obtener el nombre del autor o del realizador. Probar, a continuación, el funcionamiento de estas dos nuevas clases.
3. Ejercicio 3
Modificar las clases Book y BluRay para poder disponer de información relativa al autor o el realizador:
-
su nombre,
-
su apellido,
-
su fecha de nacimiento.
Pista: los autores y realizadores son personas.
4. Ejercicio 4
Modificar el código anterior para poder obtener rápidamente la lista de artículos relacionados con un autor o un realizador.
Correcciones
1. Ejercicio 1
La clase Article es relativamente simple. Las líneas 2 a 4 contienen la declaración de atributos que representan la información que vamos a almacenar en las instancias de la clase. Se declaran con la visibilidad private para garantizar que solo el código de la clase pueda acceder a ellas. A continuación se declaran los constructores (líneas 6 a 10 y 12 a 13), que permiten la creación de instancias en función de la información disponible.
Si bien es posible acceder directamente a los atributos en los constructores, es preferible usar los métodos setXXX para aprovechar los eventuales controles sobre los valores asignados a los atributos.
Para hacer que los atributos sean accesibles desde el exterior de la clase, cada uno debe disponer de los métodos getXXX y setXXX, que permiten recuperar o modificar su valor.
El método toString (líneas 39 a 41) ofrecen una representación textual de la instancia.
En la clase main, el método main (líneas 2 a 15) crea dos instancias de la clase Article:
-
usando en primer lugar el constructor por defecto (líneas 4 a 7), después inicializando cada atributo,
-
o directamente llamando al constructor con argumentos (línea 9).
A continuación se muestran las dos instancias, bien llamando a la función test (líneas 17 a 21), que definen un formato de visualización concreto, o invocando directamente al método toString.
En este último caso, el formato es el definido en la clase Article.
/********************* Article **********************/
1. public class Article {
2. private int reference;
3. private String designation;
4. private double price;
5.
6. public Article(int reference, String designation, double price) {
7. this.reference = reference;
8. this.designation = designation;
9. this.price = price;
10. }
11.
12. public Article() {
13. }
14.
15. public int getReference() {
16. return...