Programacion II

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 162

REPÚBLICA BOLIVARIANA DE VENEZUELA

UNIVERSIDAD PRIVADA DR. RAFAEL BELLOSO CHACÍN


VICERRECTORADO ACADÉMICO
FACULTAD DE INGENIERÍA
ESCUELA DE INGENIERÍA INFORMATICA

Programación II
Primera Edición

Ing. Ángel Pérez

Fondo Editorial
Maracaibo - Venezuela
Año 2022
Ing. Ángel Pérez

Programación II
Primera Edición

No está permitida la reproducción total o parcial de este libro, ni su


tratamiento o procesamiento informático, ni la transmisión de ninguna
forma o por cualquier medio, ya sea electrónico, mecánico, por fotocopia,
por registro u otro método, así como la distribución de ejemplares mediante
alquiler o préstamo público, sin el permiso previo y por escrito de los
titulares del copyright.

DERECHOS RESERVADOS 2022


Fondo Editorial de la Universidad Privada “Dr. Rafael Belloso Chacín”
Circunvalación Nº 2, frente a la Plaza de Toros. Maracaibo, Venezuela
Correo Electrónico: fondurbe@urbe.edu
Página Web: www.urbe.edu/investigacion/fondoeditorial

HECHO EL DEPÓSITO DE LEY

ISBN: 978-980-414-068-6
Depósito Legal: ZU2019000161

Edición de texto: Dra. María Villalobos.


Maquetación del libro: Lcdo. Roberto Wingyin He Chow.
Diseño de portada: Lcdo. Roberto Wingyin He Chow.
Edición gráfica: Lcda. María J. Paloscia.
Imágenes proporcionadas por el autor.

Publicación electrónica - Año 2022.


Publicado por el Fondo Editorial en conjunto con la Dirección de Tecnología
de la Información de la Universidad Privada Dr. “Rafael Belloso Chacin”.
Maracaibo - Venezuela. Teléfono: +58 261 200-URBE (8723).
Programación II
Primera Edición
Autoridades Universitarias
Dr. Oscar Belloso Medina
Rector Fundador/Presidente del Consejo Superior
Dr. Oscar Belloso Vargas
Rector
Dr. Mike González Bermúdez
Vicerrector Académico
MSc. Ángel Alexander Villasmil Rangel
Vicerrector Administrativo
Dr. Humberto Perozo Reyes
Secretario
Dra. Janeth Hernández Corona
Decana de Investigación y Postgrado
Dr. Plácido Martínez Paz
Decano de la Facultad de Ingeniería
Dra. Betty Margarita Galavíz Ramírez
Decana de la Facultad de Ciencias Administrativas
Dra. Lisbeth Fuenmayor Leal
Decana de la Facultad de Ciencias Jurídicas y Políticas
Dra. Marilyn Lescher
Decana de la Facultad de Humanidades

Dra. Janett del Valle Pirela González


Decana de la Facultad de Ciencias de la Informática
Dra. Adinora Oquendo Garcés
Decana de Extensión

Fondo Editorial
2022
DEDICATORIA

A mis padres y suegros, ejemplos de fe y sacrificio

A mis hijos, la razón de luchar por un país y un mundo mejor


RECONOCIMIENTOS

Al Fondo Editorial Urbe, por el apoyo prestado en la finalización de esta


obra.

Ing. Ángel Pérez


INTRODUCCION GENERAL
Primeramente, Java cambió el curso de la programación de dos formas
importantes. Cabe destacar, a través de la incorporación de funciones
que facilitaron la creación de aplicaciones orientadas a la web, lo cual
ubicó a Java como el primer lenguaje listo para internet en el mundo. En
segundo lugar, Java dió un paso adelante en el diseño de lenguajes de
programación, a través de aspectos como los siguientes:
• Redefinió el paradigma de objetos
• Depuró el manejo de las excepciones
• Integró por completo el uso de varios subprocesos en el lenguaje
• Creó un código de objetos transportable llamado código de bytes
• Como consecuencia del aspecto anterior, un programa desarrollado
en Java puede ser ejecutado en varias plataformas diferentes
Actualmente, una gran parte de las aplicaciones que se desarrollan
están orientadas a la web o descansan en el paradigma de la programación
orientada a objetos, lo cual resalta la importancia de Java para la
programación y le asegura un lugar en la historia de los lenguajes de
computación. Enmarcado en lo anteriormente dicho, se presenta este
libro, cuyo propósito es ofrecer las herramientas básicas necesarias para
iniciar al estudiante en la utilización del lenguaje de programación Java.
Los aspectos mostrados anteriormente, hacen de Java un lenguaje de
programación con múltiples opciones y propósitos generales, diseñado
para el mundo moderno conectado en red. Esto significa que Java resulta
adecuado para casi todos los tipos de programación, desde aplicaciones
para el área empresarial, pasando programas de uso personal, continuando
con aplicaciones que se ejecutan en un celular inteligente, hasta la
programación de tarjetas inteligentes.
Por ello, es importante enseñar este tipo de programación desde
los primeros cursos, a fin de que el estudiante sea capaz de adquirir la
habilidad para aplicar los conocimientos en el lenguaje de manera efectiva
y sistemática durante el desarrollo de aplicaciones (software o programas).
En este orden, el texto se encuentra estructurado en cuatro unidades.
La primera unidad, denominada programación orientada a objetos y java
trata de presentar al estudiante el paradigma de la programación orientada
a objetos, como una forma de construir software de forma rápida,
correcta y económica y, para su implementación, se hace uso de Java
como el lenguaje de programación, por lo que se hace una introducción
a: estructura de un programa, tipos de datos utilizados, instrucciones y la
manera correcta de utilizarlas.
La segunda unidad, denominada clases, métodos y herencia, inicia presentando
los conceptos abstracción, encapsulado y polimorfismo, claves para implementar
la programación orientada a objetos. Los objetos, o más exactamente, las clases
de las que provienen los objetos, son esencialmente componentes de software
reutilizables.
Casi cualquier sustantivo (fecha, automóvil, persona, entre otros), puede
ser razonablemente representado como un objeto de software en términos de
atributos (por ejemplo, nombre, color y tamaño) y comportamientos o métodos
(por ejemplo, cálculo, movimiento y comunicación), por lo que se hace necesario
limitar su uso, a través del control de acceso. La herencia permite establecer una
relación de jerarquía entre clases afines (Empleado y Obrero con Trabajador, por
ejemplo), creando las superclases y subclases.
La tercera unidad, denominada recursividad, se aplica a métodos (un método
capaz de invocarse a sí mismo se conoce como método recursivo). Un método
recursivo puede llamarse a sí mismo ya sea directa o indirectamente a través
de otro método. Este concepto se compara al de iteración (repetición de un
proceso), como enfoque alternativo de solución de problemas.
La cuarta unidad, denominada flujos y archivos, se enfoca en el almacenamiento
de los datos manejados por un programa, de tal manera que, a pesar de que haya
terminado la ejecución de dicho programa, ellos se encuentren disponibles en
otras ocasiones (persistencia de los datos, a través de archivos). Las operaciones
sobre los datos (escritura y/o lectura en archivo, entre otros) se implementa a
través del uso de flujos.
OBJETIVOS DEL PROGRAMA

OBJETIVO GENERAL

• Manejar y aplicar de forma eficiente los términos básicos de la


programación orientada a objetos, asociándola con estructuras de
datos simples.

OBJETIVOS ESPECÍFICOS

• Definir la terminología básica de la programación orientada a objetos


aplicando las instrucciones básicas del lenguaje de programación
Java.

• Utilizar de forma eficiente la herencia para minimizar líneas de


código de un programa.

• Aplicar recursividad para la resolución de problemas.

• Resolver problemas utilizando archivos como estructura de datos.


ÍNDICE GENERAL

DEDICATORIA...................................................................................5
RECONOCIMIENTOS.......................................................................7
INTRODUCCIÓN GENERAL..................................................................9
OBJETIVOS DEL PROGRAMA.............................................................11
OBJETIVO GENERAL.........................................................................11
OBJETIVOS ESPECÍFICOS..................................................................11

UNIDAD I. PROGRAMACIÓN ORIENTADA A OBJETOS Y JAVA

1. DEFINICIONES BÁSICAS................................................................21
1.1. CLASES Y OBJETOS ................................................................21
1.2. EL LENGUAJE JAVA ................................................................22
2. ORIGEN DE JAVA Y OBJETIVO PRINCIPAL DE SU TECNOLOGÍA ....23
2.1. ORIGEN DE JAVA...................................................................23
2.2. OBJETIVO PRINCIPAL DE SU TECNOLOGÍA ............................25
3. ESTRUCTURA DE UN PROGRAMA EN JAVA....................................26
4. DECLARACIÓN DE CLASES Y ATRIBUTOS.......................................29
4.1. DECLARACIÓN DE CLASES......................................................29
4.2. DECLARACIÓN DE ATRIBUTOS................................................31
5. TIPOS DE DATOS E INICIALIZACIÓN DE VARIABLES.......................34
5.1. TIPOS DE DATOS...................................................................34
5.2. TIPOS DE DATOS PRIMITIVOS...............................................35
5.3. DECLARAR (CREAR) VARIABLES.............................................36
5.4. DECLARACIÓN DE VARIABLE TIPO INT.................................37
5.5. OTRAS FORMAS DE DECLARACIÓN DE VARIABLES................38
5.6. DATOS NO PRIMITIVOS: ARREGLOS.....................................38
5.7. ACCESO A LOS ELEMENTOS DE UN ARREGLO..................39
5.8. CAMBIAR UN ELEMENTO DEL ARREGLO..............................39
5.9. LONGITUD DE UN ARREGLO...............................................40
5.10. RECORRIDO DE UN ARREGLO...............................................40
5.11. RECORRIDO DE UN ARREGLO USANDO FOR-EACH................41
5.12. SINTAXIS DE LA INSTRUCCIÓN............................................41
5.13. ARREGLOS MULTIDIMENSIONALES.......................................42
5.14. DEFINICIÓN DE UN ARREGLO BIDIMENSIONAL....................43
5.15. RECORRIDO DE UN ARREGLO BIDIMENSIONAL...................44
6. OPERADORES (ARITMÉTICOS, RELACIONALES Y LÓGICOS) Y
EXPRESIONES ...................................................................................45
6.1. OPERADORES ARITMÉTICOS................................................45
6.2. OPERADORES RELACIONALES..............................................47
6.3. OPERADORES LÓGICOS.......................................................49
6.4. OPERADORES DE ASIGNACIÓN..........................................50
7. SENTENCIAS DE DECISIÓN Y DE CICLO..........................................52
7.1. INSTRUCCIONES DE DECISIÓN...............................................52
7.2. INSTRUCCIÓN IF-ELSE............................................................53
7.3. INSTRUCCIÓN ELSE-IF............................................................54
7.4 INSTRUCCIÓN IF-ELSE ABREVIADO (OPERADOR TERNARIO)...55
7.5. INSTRUCCIÓN SWITCH (SEGÚN SEA)......................................55
7.6. CICLO – REPETICIÓN – ITERACIÓN..........................................57
7.6.1. INSTRUCCIÓN WHILE.......................................................57
7.6.2. INSTRUCCIÓN DO-WHILE.................................................58
7.6.3. INSTRUCCIÓN FOR...........................................................59
7.6.4. INSTRUCCIÓN FOR-EACH..................................................60
8. MANEJO DE STRING......................................................................61
9. COMPILACIÓN DE PROGRAMAS....................................................63
9.1. EDICIÓN................................................................................64
9.2. COMPILACIÓN........................................................................64
9.3. CARGA DEL PROGRAMA EN MEMORIA.......................................65
9.4. VERIFICACIÓN Y EJECUCIÓN DEL CÓDIGO BINARIO..................65
ACTIVIDADES DE AUTOEVALUACION...............................................67

UNIDAD II. CLASES, MÉTODOS Y HERENCIA

1. ABSTRACCIÓN, ENCAPSULADO Y POLIMORFISMO...........................70


1.1. ABSTRACCIÓN.......................................................................70
1.2. ENCAPSULACIÓN...................................................................71
1.3. POLIMORFISMO.....................................................................73
2. CONTROL DE ACCESO A LOS MIEMBROS DE CLASES..................75
3. PASO DE OBJETOS A MÉTODOS..................................................79
4. RETORNO DE OBJETOS................................................................81
5. SOBRECARGA DE MÉTODOS Y CONSTRUCTORES...........................83
5.1. SOBRECARGA DE MÉTODOS..................................................83
5.2. SOBRECARGA DE CONSTRUCTORES.......................................86
6. DESCRIPCIÓN DE HERENCIA........................................................89
7. VENTAJAS DE LA HERENCIA.......................................................91
8. SUPERCLASES Y SUBCLASES.........................................................92
ACTIVIDADES DE AUTOEVALUACION...............................................99

UNIDAD III. RECURSIVIDAD

1. DEFINICIÓN................................................................................102
2. MÉTODOS RECURSIVOS..............................................................104
3. RECURSIVIDAD VS. ITERACIÓN..................................................106
4. RESOLUCIÓN DE PROBLEMAS CON RECURSIÓN.........................107
4.1. RECORRIDO EN PREORDEN...................................................108
4.2. RECORRIDO EN INORDEN.....................................................108
4.3. RECORRIDO EN POSORDEN..................................................108
5. MÉTODOS DE ORDENACIÓN RECURSIVOS...................................110
5.1. ORDENACIÓN POR MEZCLA...................................................111
5.2. ORDENACIÓN RÁPIDA (QUICKSORT).....................................113
ACTIVIDADES DE AUTOEVALUACION...............................................117

UNIDAD IV. FLUJOS Y ARCHIVOS

1. FLUJOS.......................................................................................120
1.1. DEFINICIÓN.........................................................................120
1.2. FLUJOS. JERARQUÍA DE CLASES.............................................121
1.2.1. LECTURA DE LA CONSOLA DE ENTRADA...........................125
1.2.2. LECTURA DE CARACTERES...............................................126
1.2.3. LECTURA DE CADENAS....................................................128
1.2.4. LA CLASE PRINTWRITER.................................................129
1.2.5. LA CLASE SCANNER........................................................130
1.3. CLASES FILEINPUTSTREAM Y FILEOUTPUTSTREAM.................132
1.4. CLASES BYTEARRAYINPUTSTREAM Y BYTEARRAYOUTPUTSTREAM
.................................................................................................134
1.5. FILTROS...............................................................................136
1.6. CLASES DATAINPUTSTREAM Y DATAOUTPUTSTREAM...............137
1.7. CLASE PRINTSTREAM............................................................139
2. ARCHIVOS..............................................................................142
2.1. DEFINICIÓN.........................................................................142
2.2. CLASE FILE...........................................................................143
2.3. TIPOS DE ARCHIVOS.............................................................151
2.3.1. POR EL TIPO DE ACCESO A LOS REGISTROS DE DATOS........151
2.3.2. POR EL TIPO DE DATO ALMACENADO................................152
2.3.3. POR LA OPERACIÓN.........................................................153
2.4. ARCHIVO FUENTE.................................................................153
2.5. ARCHIVOS DE ENTRADA........................................................154
2.6. ARCHIVOS DE SALIDA............................................................156
ACTIVIDADES DE AUTOEVALUACION...............................................159

REFERENCIAS BIBLIOGRÁFICAS.......................................................161
UNIDAD I
PROGRAMACIÓN ORIENTADA A
OBJETOS Y JAVA
Programación II 21

UNIDAD I
PROGRAMACIÓN ORIENTADA A OBJETOS Y JAVA

1. DEFINICIONES BÁSICAS
La programación orientada a objetos es un modelo que presenta una
teoría que conviene estudiar para ampliar los conocimientos. Considerando
lo expuesto anteriormente, se busca que el estudiante reoriente su manera
de pensar, adquiriendo el lenguaje específico a aplicar en los procedimientos
planificados con Java. En este punto, se muestra brevemente los conceptos
básicos de la programación orientada a objetos desde un punto de vista
global, como por ejemplo, los términos clase y objeto y lenguaje Java.

1.1. Clases y objetos


Como se indicó en la introducción, el objetivo del libro es presentar
la programación orientada a objetos a través de Java, como lenguaje
de programación a usar. Esto establece la necesidad de revisar algunas
nociones fundamentales sobre este estilo, antes de empezar a escribir
programas reales. Desde este punto de vista, los dos conceptos más
importantes son los de clases y objetos.
Primeramente, en cuanto al término clase, Aguirre (2020), la
considerada como un patrón o plantilla en la que se basan objetos que son
similares. En tal sentido, cuando un programa crea un objeto de una clase,
proporciona datos para sus variables y el objeto puede entonces utilizar
los m´etodos que se han escrito para la clase. Por tanto, todos los objetos
creados a partir de la misma clase comparten los mismos procedimientos
para sus métodos, también tienen los mismos tipos para sus datos, pero
los valores pueden diferir.
Seguidamente, para Arroyo (2010), una clase es una especificación del
comportamiento abstracto y del estado abstracto de un tipo de objeto.
Las clases son instanciables, por lo que a partir de ellas se pueden crear
instancias de objetos individuales (es el equivalente a una clase concreta
en los lenguajes de programación). En este orden, de enunciados, una
clase puede extender, como máximo, a otra clase.
Según Wu (2001), una clase es un tipo de molde o plantilla usado
por el programa para crear objetos (o instancias de una clase). Luego,
tomando como referencia lo explicado anteriormente, se tiene la siguiente
secuencia: Creación del programa o aplicación -> Definición de las clases
requeridas -> Ejecución del programa -> Generación o creación de objetos.
22 Ángel Pérez

Es conveniente acotar que un programa escrito en un lenguaje de tercera


generación (antecesora de la programación orientada a objetos) estaba
dirigido a los datos (o variables encargadas de su almacenamiento) y las
instrucciones requeridas para su manejo, de una manera estructurada.
En continuidad con las ideas anteriores, Wu (2001), menciona que
un objeto es cualquier cosa, tanto tangible como intangible que pueda
ser imaginada. Luego, tomando dicha afirmación como referencia, un
programa desarrollado en este estilo de programación, consistirá de una
serie de objetos que interactúan entre sí. Como ejemplo, una aplicación
orientada al área bancaria puede tener como objetos a Cuenta, Cliente,
Operación (o Transacción), entre otros.
Como se puede inferir, dichos objetos, requieren datos y las operaciones
establecidas para manejarlos (instrucciones en el programa). Continuando
con el ejemplo anterior, un objeto Cuenta puede contener los siguientes
datos: número de cuenta, nombre del cuentahabiente, fecha de apertura,
saldo inicial, saldo actual, entre otros. Operaciones a realizar: apertura de
cuenta, depositar, transferir, retirar.
Al respecto, un objeto tiene existencia dinámica a partir del momento
en que se crea en el programa (a través de instrucciones) y mantiene su
vigencia hasta el fin del programa o hasta que cumpla el objetivo para el
cual fue creado. La creación de un objeto depende del suministro de una
definición: la clase a la cual está asociado, la cual, es el segundo concepto
importante de la programación orientada a objetos, como se mencionó
anteriormente.
Continuando con la idea anterior, en la programación orientada a
objetos, las variables que manejan (o almacenan) los datos reciben el
nombre de atributos (características o variables de instancia), mientras
que las instrucciones (u operaciones) encargadas de su manejo reciben el
nombre de métodos (o comportamientos). En el ejemplo de la aplicación
orientada al área bancaria, un objeto tipo cuenta (una instancia de la
clase cuenta) puede estar en proceso de apertura (inicio de operaciones),
mientras que en una segunda cuenta se realiza un retiro y en una tercera
cuenta se realiza un depósito.
1.2. El lenguaje Java
Como lenguaje de programación, Java, posee un conjunto de palabras
reservadas (propias; conocidas en inglés como keywords) y una sintaxis o
conjunto de reglas que permiten especificar: tipos de datos a usar, forma
de terminar una instrucción, inicio y fin de una clase, inicio y final de
un método, cómo documentar un programa, cómo manejar las librerías
(conjuntos de programas desarrollados para ampliar la capacidad de
Programación II 23

proceso de una aplicación que se esté desarrollando), entre otros aspectos


a cubrir en la elaboración de programas. El conjunto de palabras reservadas
proviene del inglés, lo cual lo hace bastante inteligible, para alguien que
conozca dicho idioma.
En este orden de ideas, Java tiene sus orígenes en el lenguaje C (del
cual procede su sintaxis) y C++ (del cual procede su orientación a objetos).
Como especifican Deitel y Deitel (2012), un objetivo clave de Java es la
capacidad de escribir programas que se pueden ejecutar en una gran
variedad de plataformas (sistemas operativos o software) y dispositivos
(o hardware).
Esto significa, teóricamente, un programa desarrollado en Unix puede
ser ejecutado en Linux o en Windows, además, se puede ejecutar en un
computador de escritorio (desktop) o en una portátil (laptop o minilaptop)
o en teléfono celular inteligente (Smartphone). Esta característica se
conoce como portabilidad.

2. ORIGEN DE JAVA Y OBJETIVO PRINCIPAL DE SU TECNOLOGÍA


Java ha sido un lenguaje para trabajar de manera segura, debido a
la implementación de librerías y diversos contextos. Es por ello que se
debe considerar estudiarla en tres ambientes diferentes: seguridad en la
maquina virtual, en las aplicaciones y en la red. Por tanto, se da a conocer
el origen de Java desde sus inicios.

2.1. Origen de Java


Según Cosmina (2018), todo comenzó en 1990, cuando un grupo
de ingenieros, entre los cuales se encontraba James Arthur Gosling
(reconocido como el padre del lenguaje Java), en una compañía llamada
Sun Microsystems, inició el desarrollo de un lenguaje de programación
en principio conocido como Oak. Finalmente, en 1996 (es decir, cinco
años después), hace su aparición en la web Java 1.0 para Linux, Solaris,
Mac, y Windows, el cual fue liberado de esta manera, para buscar una
rápida adopción por parte de la comunidad de desarrolladores, lo cual fue
logrado de manera exitosa. Como resumen de la evolución del lenguaje,
se presenta la siguiente tabla:
24 Ángel Pérez

Cuadro 1
Evolución del lenguaje Java

Año Versión Aspectos relevantes


1998 1.2 Plataforma Java 2, compuesta de tres partes
o niveles: J2SE (Java 2 Standard Edition),
orientada al desarrollo de aplicaciones
de escritorio y servidores; J2EE (Java 2
Enterprise Edition), orientada a servicios web
y computación distribuida; J2ME (Java 2 Micro
Edition), orientada al desarrollo de aplicaciones
para dispositivos móviles
2000 1.3 Clases Java, APIs XML.
2002 1.4 • Primer año de la Java Community Process,
como parte colaboradora en el proceso de
desarrollo de una versión de Java. (esta
versión se conoce como JSR 59, del inglés Java
Specification Requests).
• Soporte para el protocolo IPv6
• Desarrollo de APIs de registro de eventos
(logging) y de procesamiento de imágenes

2004 1.5 • Renombrada como J2SE 5.0.


• Mejora de la instrucción for each, para
iterar sobre colecciones y arreglos
• Semántica mejorada para programas
multihilo (multithreaded)
• Introducción de la clase Scanner
2006 Java SE 6 • Renombrada como J2SE 5.0.
• Mejora de la instrucción for each, para
iterar sobre colecciones y arreglos
• Semántica mejorada para programas
multihilo (multithreaded)
• Introducción de la clase Scanner

2008 Java FX • JavaFX se usa para crear interfaces


1.0 SDK gráficas de usuario para cualquier plataforma.
Programación II 25

2011 Java SE 7 • Primera versión liberada bajo Oracle


• Soporte para interacción con lenguajes
dinámicos (es decir, el código Java puede usar
código de lenguajes como C)
• Optimización de la JVM, lo cual se
traduce en un menor consumo de memoria.
• Liberado JavaFX 2.0, como parte de
esta versión

2014 Java SE 8 • Nueva API para el manejo de fecha y


hora
• Nueva forma de hacer procesamiento
paralelo, a través del uso de flujos (streams, en
inglés)
• Integración mejorada con JavaScript

2017 Java SE 9 • Cambio en el diseño de la plataforma:


introducción de los módulos Java
• La herramienta Java Shell, una interfaz
de líneas de comando interactiva, para ejecutar
Marzo Java SE • Optimización del proceso recolector de
20, 2018 10 (Java basura, lo cual redunda en un uso más eficiente
18.3) de la memoria.

Fuente: Peréz (2022)

2.2. Objetivo principal de su tecnología


Un aspecto clave en internet es el hecho de que conecta diferentes
tipos de computadores y sistemas operativos. Si un programa en Java
se ejecuta en cualquier computador conectado a internet, se requiere un
mecanismo que permita que el mismo código sea ejecutado en cualquier
computador, independientemente del hardware (CPU) o sistema operativo.
Como se mencionó en el punto anterior, esta característica se conoce como
portabilidad.
Según Schildt (2017), dicho mecanismo está compuesto, por un lado,
del código binario (el resultado de la compilación del código fuente, ver
punto anterior), el cual, no es un código para ser ejecutado directamente
por el sistema operativo. Por otro lado, se encuentra la máquina virtual de
Java (JVM), la cual se encarga de interpretar y ejecutar el código binario.
En este sentido, la JVM actúa como intermediario entre el programa y
el sistema operativo, lo cual permite la independencia respecto de la
máquina, haciendo que el código sea portable entre máquinas distintas.
26 Ángel Pérez

3. ESTRUCTURA DE UN PROGRAMA EN JAVA


En Java, la unidad fundamental de construcción de programas es la
clase, según lo especifica Schildt (2017). Una clase, como se indicó en el
punto 1, es una plantilla para una entidad discreta (objeto), que contiene
atributos y comportamientos. La clase define la estructura básica de un
objeto; éste es creado (instanciado) al momento en que se ejecuta el
programa. A continuación, se presenta un ejemplo básico para ilustrar la
estructura de una clase:

/********************************************************
Ejemplo de una clase en Java.
Este ejemplo muestra una plantilla de programa para aplicaciones Java
sencillas.
Con esta plantilla se puede codificar, compilar, ejecutar y depurar
cualquier
aplicación Java, en cualquier plataforma.
********************************************************/

import java.util; //Declaración import


public class Principal
{
public static void main(String[] args) {
System.out.println(“Hola Mundo”); //Imprime Hola Mundo
en cónsola
}
}

El ejemplo comienza con un comentario que describe lo que hace el


programa. Cada línea de código que se ejecuta en Java debe estar dentro
de una clase. En el ejemplo, se ha llamado Principal a la clase. Una clase
siempre debe comenzar con una primera letra mayúscula. Las llaves ({,})
se usan como delimitadores, tanto en la clase como en cada método,
para señalar el comienzo y el final de cada uno. Según Schildt (2017),
Java distingue entre mayúsculas y minúsculas; las palabras public, class,
Programación II 27

static, void, main, out y println son reservadas y se escriben en minúscula,


mientras que String y System se escriben con mayúscula.
Asi mismo, el ejemplo muestra que las líneas de código no están todas
escritas al inicio de la línea; la segunda línea comienza dos espacios más
adelante, mientras que la tercera línea ocurre algo igual. Dicho espaciado
se conoce como indentación y permite ver que la segunda línea (en donde
inicia el método main ()) y subsiguientes forman parte de Principal.
De manera equivalente, la cuarta línea (la primera instrucción del
programa) forma parte de main (). En este sentido, Schildt (2017), el
indentado de las líneas de una clase no es obligatorio, pero constituye una
de las prácticas recomendadas en este campo y contribuye a escribir un
programa claro y fácil de mantener.
El ejemplo anterior, debe ser guardado en un archivo, antes de ser
ejecutado. El nombre del archivo java debe coincidir con el nombre de
la clase. El archivo se guarda usando el nombre de la clase y añadiendo
“.java” al final del nombre del archivo; en el ejemplo anterior, el archivo
que va a alojar el código se debe llamar Principal.java. Si el nombre de
la clase y el nombre del archivo no coinciden, se generará un error de
sintaxis. El método main () es obligatorio en todos los programas Java que
se ejecuten localmente:

public static void main(String[] args)

Considerando los enunciados anteriores, las palabras que preceden a


main () son de uso obligatorio (la omisión de una o varias genera un error
de sintaxis). El método main () se encuentra dentro de la clase (MiClase,
en este ejemplo). Cualquier código o conjunto de instrucciones dentro del
método main () (es decir, que se encuentre dentro del par de {} ubicados
después de main ()) será ejecutado. El método System.out.println() sirve
para imprimir una línea de texto (”¡Hola Mundo!”; los mensajes o cadenas
deben ser escritos entre comillas) en la pantalla:

System.out.println(“¡Hola Mundo!”);

Schildt (2017), menciona que, en Java, cada instrucción de código debe


terminar con un punto y coma. Los términos public static void son palabras
reservadas que califican al método. En el caso de main (), son obligatorias.
28 Ángel Pérez

Los términos String [] args corresponden a los argumentos que se pueden


usar en el método main (); en este caso, un arreglo llamado args de tipo
cadena. Los comentarios pueden ser usados para explicar el código, y para
hacerlo más legible.
También se pueden utilizar para evitar la ejecución de determinadas
instrucciones, cuando se prueba código alternativo. Los comentarios de
una línea comienzan con dos barras oblicuas (//). Cualquier texto entre //
y el final de la línea es ignorado por Java (no se ejecutará). Este ejemplo
utiliza comentarios de varias líneas (comienzan con /* y terminan con */;
ver el principio del ejemplo anterior), y comentarios de una sola línea (al
lado de una instrucción; ver ejemplo anterior); también, se puede usar
antes (o después) de una línea de código:

// La siguiente instrucción imprime Hello World en cónsola


System.out.println(“Hello World”);

Lo más relevante en la inserción de comentarios a lo largo del programa,


es que contribuye a su legibilidad y constituye otra práctica recomendada
de estilo de programación. Adicionalmente, los comentarios dentro de un
programa forman parte de la documentación del mismo; a mayor cantidad
de comentarios, mejor documentado quedará el programa. Si el comentario
a escribir requiere más de una línea, se puede usar /* para indicar el inicio
y */ para indicar el final del comentario, como se muestra a continuación:

/*
Valida que saldoInicial sea mayor que 0.0; si no lo es,
la variable saldo se inicializa con el valor predeterminado 0.0
*/

Así, el ejemplo sigue la estructura general de un programa en Java,


como se muestra a continuación:
Programación II 29

Figura 1. Estructura general de una clase


Fuente: Wu (2001)

4. DECLARACIÓN DE CLASES Y ATRIBUTOS


Como se observó, primeramente, en los puntos anteriores de esta
unidad I: Programación orientada a objetos, las clases y los atributos
son términos relevantes en Java. En este orden de ideas, una clase es
considerada una abstracción, razón por la cual, permite describir las
cualidades de los objetos, así todos los objetos de una misma clase tienen
los mismos atributos, ante esto, se dará a conocer en qué consiste la
declaración de ambos términos.

4.1. Declaración de clases


Como se ha indicado anteriormente, Java es un lenguaje de programación
orientado a objetos. En este orden, todo en Java está asociado a clases y
objetos, junto con sus atributos y métodos. Por ejemplo: en la vida real, un
carro es un objeto. Por tanto, el carro tiene atributos, como peso y color,
y métodos, como conducción y frenado.
30 Ángel Pérez

En criterio de Eck, (2019), una clase representa un conjunto de


objetos que comparten la misma estructura y comportamientos. La clase
determina la estructura especificando las variables y el comportamiento a
través de los métodos. Así, una clase es como un constructor de objetos, o
un “plano” para crear objetos. Como se vió en el punto anterior, para crear
una clase, se utiliza la palabra clave class:

public class MiClase {


int x = 5;
public static void main(String[] args){
System.out.println(x);
}
}

Para Joyanes, y Zahonero, (2008), la declaración de una clase puede


incluir el modificador public como prefijo (ver ejemplo anterior), en cuyo
caso puede ser vista (o utilizada) por las clases que se encuentren en su
entorno de trabajo (paquete; conjunto de clases agrupadas en un directorio
o carpeta). Los atributos de una clase son las variables de la misma.
En el ejemplo anterior, se ha creado una clase llamada MiClase y
se ha definido un atributo, x, al cual se le ha asignado el valor inicial de
5. Otros términos para los atributos: campos o variables de instancia. En
Java, un objeto se crea a partir de una clase. En el ejemplo anterior, para
crear un objeto de MiClase, se especifica el nombre de la clase, seguido
del nombre del objeto, y se utiliza la palabra clave new, como se muestra
a continuación:

public class MiClase {


int x = 5;
public static void main(String[] args) {
MiClase miObjeto = new MiClase(); // Creación del objeto
System.out.println(miObjeto.x);
}
}
Programación II 31

4.2. Declaración de atributos


Según Blasco (2019), refiere que, en Java, las variables se declaran
haciendo parte de una clase o de un método. Por tanto, las clases que
descienden de una principal pueden utilizar los mismos atributos de esta.
En este orden, todo identificador que se declara, como el nombre de las
clases se le debe dar (acceso) y si es constante (final) o no. Además, se
debe decir el tipo, lo cual pertenece a algunos de los tipos primitivos que
posee Java, obien de alguna clase a la que tenga acceso; lo último que se
da es el identificador.
En estos términos, el mismo autor mencionado anteriormente, acota
que, en la declaración de atributos, el sistema de máquina le asigna una
localidad, esto es, un espacio en memoria donde guardar valores del tipo
especificado, asimismo, la cantidad de espacio depende del tipo. Aunado a
esto, a los atributos que se refieren a una clase se les reserva espacio para
una referencia, siendo esta la posición en el heap donde quedará el objeto
que se asocie a esa variable.
Se puede señalar que, en el ejemplo anterior, se creó un objeto llamado
miObjeto. Luego de la palabra new, aparece MiClase(), la cual representa
al constructor de la clase. A través de la variable miObjeto, se imprime
el valor de la variable x. El operador “.” sirve para cualificar un atributo,
indicando el objeto de procedencia. Se pueden crear varios objetos de la
misma clase, como se puede ver en el siguiente fragmento de código; el
acceso al valor del atributo x se realiza a través del objeto correspondiente,
como se muestra a continuación:

MiClase miObjeto1 = new


MiClase(); // Creación de miObjeto 1
MiClase miObjeto2 = new
MiClase(); // Creación de miObjeto 2
System.out.println(miObjeto1.x); // Impresión del valor de x en
miObjeto1
System.out.println(miObjeto2.x); // Impresión del valor de x en
miObjeto2
}
}
32 Ángel Pérez

Continuando con la misma idea, en el ejemplo anterior, la impresión


de x en cada objeto va a dar el mismo resultado o valor. A través de
una instrucción de asignación, se puede modificar el valor de un atributo.
El atributo, al momento de su definición, tiene un valor inicial. Luego
que se crea un objeto, se tiene acceso a su(s) atributo(s) y a través de
instrucciones de asignación se puede(n) cambiar el(los) valor(es) del(los)
atributo(s). En este caso, la asignación realizada es una sobreescritura,
como se muestra en el siguiente ejemplo:

public class MiClase {

El constructor es una especie de método. Según Cosmina, I (2018:109),


una clase no puede existir sin un constructor; de lo contrario, no puede
ser instanciada. Por eso, el compilador genera uno, si no se ha declarado
explícitamente. El constructor lleva el mismo nombre de la clase seguido
de (); obligatorio.

int x = 10; // Valor inicial del atributo x


public static void main(String[] args) {
MiClase miObjeto = new MiClase(); // Creación de miObjeto
miObjeto.x = 25; // A través de una asignación, x vale ahora 25
(sobreescritura)
System.out.println(miObjeto.x);
}
}

Mostrando el ejemplo anterior, el valor inicial de x es 10. Al crear la


variable miObjeto, el valor del atributo x es cambiado a 25 o a cualquier otro
valor, tantas veces como sea requerido. Si no se desea la sobreescritura, se
declara el atributo como final, que lo convierte en una constante:

final int x = 10;

Luego, si se intenta crear la variable mi Objeto y cambiar el valor de x,


se va a generar un error, ya que el valor de x no puede cambiar. La palabra
Programación II 33

public class MiClase {


int x = 5;

public static void main(String[] args) {


MiClase miObjeto1 = new MiClase(); // Objeto 1
MiClase miObjeto2 = new MiClase(); // Objeto 2
miObjeto2.x = 25; //Modificación del atributo x de
miObjeto2
System.out.println(miObjeto1.x); // Imprime 5
System.out.println(miObjeto2.x); // Imprime 25
}
}

Cabe destacar, en los ejemplos vistos anteriormente, muestran cómo


crear uno o varios objetos de una clase. En todos estos, sólo hay una
variable de instancia o atributo definido. Se pueden crear objetos con
varios atributos, lo cual, requiere la definición de variables adicionales en
la clase origen. El tratamiento de las variables adicionales, es el mismo
que se ha mostrado anteriormente, con un atributo, como se muestra a
continuación:

public class Persona {


String nombre = “Juan”; //Definición de nombre como variable de la
clase Persona
String apellido = “Perez”;//Definición de apellido como variable de la
clase Persona
int edad = 24; //Definición de edad como variable de la clase Persona
public static void main(String[] args) {
Persona objPersona = new Persona(); //Creación del objeto
objPersona
// Impresión de los atributos nombre y apellido de objPersona
34 Ángel Pérez

System.out.println(“Nombre: “ + objPersona.nombre + “ “ +
objPersona.apellido);
System.out.println(“Edad: “ + objPersona.edad); //Impresión del
atributo edad de objPersona
}
}

5. TIPOS DE DATOS E INICIALIZACIÓN DE VARIABLES


En criterio de Eck (2019), las instrucciones en un lenguaje de
programación y, por ende, los programas que se desarrollan con las
mismas, están orientados al manejo y tratamiento (procesamiento) de
datos. Por dato se puede entender todo tipo de valor como, por ejemplo,
los correspondientes a una persona (nombre, apellido, edad, entre otros,
los cuales permiten su identificación).
En este orden de ideas, los datos pueden ser de tipo numérico (como
la edad o el peso), caracter (‘M’ o ‘F’, para designar el género) o tipo texto
(“Bertha”, “Fernández”, como nombre y apellido, respectivamente). Como
se vio en el punto anterior, el manejo de datos se realiza a través de
variables. Cada variable debe ser definida a través de la asignación de un
tipo de datos, un nombre, y un valor (en caso de inicialización).

5.1. Tipos de datos


De acuerdo con Márquez (2021), los tipos de datos definen las
cualidades de los objetos, es decir, son considerados como un conjunto de
valores que pueden tomar operaciones que se aplican en un espacio de
almacenamiento. En este orden, depende de su clasificación y además, son
uno de los términos básicos de la programación. Mencionando, el ejemplo
de la clase Persona, los atributos nombre y apellido van precedidos de la
palabra String, mientras que el atributo edad está precedido de la palabra
int. Hay diferentes tipos de datos, los cuales se dividen en dos grupos:

• Tipos de datos primitivos: byte, short, int, long, float, double,


boolean y char.

• Tipos de datos no primitivos: String, Arrays (Arreglos) y Clases.


Programación II 35

5.2. Tipos de datos primitivos


En criterio de Arroyo (2019), el tipo de dato denominado primitivo son
de tipo númerico (enteros o reales), de tipo carácter y tipo boleano, siendo
estos los más reconocidos, y, además, para poder manipular los objetos
se tienen las referencias, estas contienen la dirección en memoria, donde
se encuentra el objeto con el que se quiere trabajar; no contine el objeto
en sí. En este orden, el tipo de dato determina los posibles valores que
puede tener un dato, así como las operaciones que se pueden hacer con
él. Debido a que los datos de tipo primitivo no son objetos y no existen
métodos asociados a ellos, la única forma de trabajar con estos datos es
mediante los operadores que existen para estos.
Al respecto, Aguirre (2020), este corresponde a un tipo de dato
predefinido por el lenguaje cuando se define a un atributo o variable de
este tipo, entonces hay que separar un espacio en memoria para guardar
su valor. los tipos de datos primitivos que exiten son: byte, short, int, long,
float, doublé, boolean y char.
Para Wu (2001), este tipo de dato especifica el tamaño y tipo de valores
que va a almacenar la variable y no tiene métodos adicionales. Como
muestra el siguiente cuadro, hay ocho tipos de datos primitivos en Java:

Cuadro 2

Tipos de datos primitivos

Tipo de Tamaño Descripción


datos
Byte 1 byte Almacena números enteros entre -128 a 127
Short 2 bytes Almacena números enteros entre -32,768 to
32,767
Int 4 bytes Almacena números enteros entre
-2,147,483,648 to 2,147,483,647
Long 8 bytes Almacena números enteros entre
-9,223.372,036.854,775.808 to
9,223.372,036,854,775,808
Float 4 bytes Almacena números decimales entre
3.4e−038 to 3.4e+038; de 6 a 7 dígitos
decimales
36 Ángel Pérez

Double 8 bytes Almacena números decimales entre


1.7e−308 to 1.7e+038; 15 dígitos decimales
Boolean 1 byte Almacena valores verdadero o falso
Char 2 bytes Almacena una letra o caracter sencillo
Fuente: Wu (2001)

El cuadro anterior muestra, los tipos numéricos y no numéricos. Los


primeros permiten la realización de operaciones aritméticas. El tipo boolean
solo maneja valores de tipo lógico (o booleano: verdadero y falso). El tipo
char permite el manejo de las letras del alfabeto (mayúscula/minúscula:
‘A’, ‘B’, …, ‘Z’, ‘a’, ‘b’, …, ‘z’), dígitos (‘0’, …,’9’), signos de puntuación (‘,’,’.’,
…), entre otros; cada valor individual debe ir encerrado entre apóstrofes.
Los tipos primitivos numéricos están divididos en dos grupos:

• Punto fijo: almacena valores enteros, positivos o negativos (como


123 o -456), sin decimales. Tipos válidos: byte, short, int y long. El tipo a
usar depende de los valores numéricos a manejar.

• Punto flotante: representan números con parte fraccional,


conteniendo uno o más decimales. Hay dos tipos: float y double.

5.3. Declarar (crear) variables


Según Márquez (2021), la declaración de variables se realiza para
contener un dato. En tal sentido, estas se declaran escribiendo el tipo
de dato seguido de su identificador y al final un punto y coma. En este
orden, si existe más de una variable del mismo tipo se puede escribir el
tipo de estas seguido de un identificador en la misma o diferente línea,
obligatoriamente, se debe definir el valor inicial en el momento de su
declaración. En continuidad con la idea, para crear una variable, se debe
especificar el tipo de dato y asignarle un valor:

tipo variable = valor;

Donde tipo es uno de los tipos de datos (como int o String), y variable
es el nombre de la variable (como x o nombre). El signo igual se utiliza
Programación II 37

para asignar valores a la variable. En el siguiente ejemplo, se crea una


variable llamada nombre, a la que se le asigna el valor “Juan” y se imprime
por consola.

String nombre = “Juan”;


System.out.println(nombre);
Ejemplo completo:

public class MiClase {


public static void main(String[] args) {
String nombre = “John”;
System.out.println(nombre);
}
}

5.4. Declaración de variable tipo int


En criterio de Marquez (2021), las variables de tipo entero o int son
aquellas que almacenan un número (ya sea positivo o negativo) no decimal.
Debido a que cuando creamos una variable se reserva memoria para ella,
cada tipo de variable reservará más o menos memoria para representar el
dato que almacenarán.
El siguiente ejemplo muestra la creación (definición o declaración) de
una variable llamada Num de tipo int, con valor inicial de 15, la cual es
mandada a imprimir en cónsola:

public class MiClase {


public static void main(String[] args) {
int myNum = 15;
System.out.println(myNum);
}
}
38 Ángel Pérez

5.5. Otras formas de declaración de variables


El siguiente extracto de código muestra que en una instrucción se
realiza la declaración de una variable y la asignación del valor se lleva a
cabo en otra instrucción, la cual puede ir a continuación o más adelante:

int Num;
Num = 15;
System.out.println(Num);

Siguiendo el formato dado inicialmente para declarar variables, se


muestran varias variables de distintos tipos de datos y su correspondiente
definición (o declaración). En particular, el segundo de los ejemplos
muestra una constante con parte decimal seguida de la letra (5.99f), lo
cual indica al compilador que la variable FloatNum va a ser inicializada con
un valor tipo float (estableciendo la igualdad de tipos para la variable y su
valor inicial):

int Num = 5;
float FloatNum = 5.99f;
char Letra = ‘D’;
boolean Booleano = true;
String Texto = “Hello”;

5.6. Datos No Primitivos: Arreglos


En criterio de Eck (2019), los arreglos se usan para almacenar múltiples
valores en una sola variable, en vez de declarar variables separadas para
cada valor. En este orden un arreglo se declara junto con las demás
variables ya sea al principio de un método específico. El rpogramador da
un nombre al arreglo, éste debe describir claramente su función. En Java,
las reglas para elegir el nombre de un arreglo son las mismas que para
cualquier otro nombre (de variable, clase, objeto o método)
Aunado a la idea anterior, se infiere entonces que, para declarar un
arreglo, se define el tipo seguido de corchetes, mientras que la inicialización
se realiza a través de la colocación de valores en una lista, separados por
coma, delimitados por comillas y encerrados dentro de llaves:
Programación II 39

String [ ] carros; // Declara carros como arreglo de valores, de tipo


String
String [ ] carros = {“Volvo”, “BMW”, “Ford”, “Mazda”}; //Inicializa la
variable carros

5.7. Acceso a los elementos de un arreglo


En cuanto a la teoría del autor Eck (2019), refiere que, para el acceso
de un arreglo, primeramente, se accede a un elemento haciendo referencia
al número de índice (variable que indica la posición del elemento dentro
del arreglo). En este orden, los índices de arreglo empiezan por 0: [0] es
el primer elemento. [1] es el segundo elemento, entre otros. Luego, si hay
n elementos en el arreglo, el primero se ubica en la posición 0, mientras,
el último se ubica en la posición n-1. A continuación, el ejemplo completo:

public class MisCarros {


public static void main(String[] args) {
String[] carros = {“Volvo”, “BMW”, “Ford”, “Mazda”}; //Inicializa la
variable carros
System.out.println(cars[0]); //Presenta el primer elemento de
variable carros (Volvo)
}
}

5.8. Cambiar un elemento del arreglo


De igual manera, para cambiar un elemento del arreglo se realiza a
través del operador de asignación, haciendo referencia a la posición del
elemento por medio del número de índice:

public class MisCarros {


public static void main(String[] args) {
String[] carros = {“Volvo”, “BMW”, “Ford”, “Mazda”};//Inicializa la
variable carros
carros[0] = “Opel”; // Cambia el valor del primer elemento de carros
a “Opel”
40 Ángel Pérez

System.out.println(carros[0]);
}
}

5.9. Longitud de un arreglo


Según Aguirre (2020), en un programa se puede conocer la longitud
de un arreglo. En tal sentido, una vez creado un arreglo no podrá cambiar
su longitud. Es decir, al desarrollar un programa nuevo, deberá pensar en
el tamaño del arreglo que necesitará y se tendrá que escribir instrucciones
para comprobar si el programa no sobrepasa los límites del mismo.
Aunado a esta idea, se infiere que la longitud de un arreglo, está
referida a cuántos elementos tiene un arreglo, para lo que se usa la
propiedad length, que es propia de las variables tipo String:

public class MisCarros {


public static void main(String[] args) {
String[] carros = {“Volvo”, “BMW”, “Ford”, “Mazda”};
carros[0] = “Opel”;
System.out.println(carros.length);//Imprime la cantidad de valores
(4)
}
}

5.10. Recorrido de un arreglo


Según las ideas expuestas con anterioridad, Aguirre (2020), menciona
que en Java un arreglo es un objeto, y se declara de la misma manera que
cualquier otra variable, generalmente, al principio de un método o de una
clase. En este orden, el programador da un nombre al arreglo al momento
de su creación.
En cuanto a la realización del recorrido de un arreglo, esta se
puede hacer usando la instrucción for (para mayor información sobre esta
instrucción, ver sección 7. (SENTENCIAS DE DECISIÓN Y DE CICLO) y la
propiedad length, para determinar la cantidad de elementos a procesar. Un
ejemplo completo se presenta a continuación:
Programación II 41

public class ListaCarros {


public static void main(String[] args) {
String[] carros = {“Volvo”, “BMW”, “Ford”, “Mazda”};
for (int i = 0; i < carros.length; i++) {
System.out.println(carros[i]);
}
}
}

5.11. Recorrido de un arreglo usando for-each


Una segunda forma de la instrucción for implementa un bucle o lazo de
estilo “para cada uno”. Como indica Schildt (2017), la teoría del lenguaje
contemporáneo ha abrazado el concepto para-cada-uno, y se ha convertido
en una característica estándar. Se puede inferir, esta instrucción permite
recorrer una colección de objetos en forma estrictamente secuencial, de
principio a fin.

5.12. Sintaxis de la instrucción


Schildt (2017), acota que la sintaxis define la estructura y apariencia
de la escritura del código Java. En este orden, queda representado de la
siguiente manera:

for (tipo variable: nombre-arreglo) { bloque de instrucciones }

Aquí, tipo específica el tipo de dato y variable especifica el nombre


de una variable de iteración que recorrerá los elementos de un arreglo
(especificado por nombre-arreglo), uno a uno, de principio a fin. Con cada
iteración del bucle, el siguiente elemento del arreglo se recupera y se
almacena en variable. El bucle se repite hasta que se han obtenido todos
los elementos de la colección.
El siguiente elemento en la sintaxis de la instrucción especifica el
conjunto de instrucciones que se van a aplicar sobre cada elemento del
arreglo. Para el caso del arreglo carros, se tiene:
42 Ángel Pérez

public class ListaCarros {


public static void main(String[] args) {
String[] carros = {“Volvo”, “BMW”, “Ford”, “Mazda”};
for (String i : carros) { //Recorrido del arreglo carros a través de
for-each
System.out.println(i); //Imprime cada elemento del arreglo carros
}
}
}

El siguiente ejemplo puede leerse así: para cada elemento String


(llamado i - como en el índice) en el arreglo carros, imprima el valor de i. El
método for-each es más fácil de escribir, no requiere un contador (usando
la propiedad length), y es más legible.

5.13. Arreglos Multidimensionales


Al respecto, López y Gutierrez (2014), los arreglos multidimensionales,
son estructuras de datos que contienen o agrupan en multiple datos de un
mismo tipo, además pueden ser tratados de manera individual, haciendo
referencia a estos con un mismo nombre. Aunado a esta idea, los arreglos
bidimensionales son un caso concreto de este tipo de arreglo porque
contiene dos índices.
Schildt (2017), acota que estos son considerados, como un arreglo que
contiene dos o más arreglos (o dimensiones). También, se puede decir que
está constituido por filas y columnas. Para crear un arreglo bidimensional,
se agrega cada arreglo en su propio conjunto de llaves:

int [ ] [ ] misNumeros = { {1, 2, 3, 4}, {5, 6, 7, 8} };

“Mis Números” es un arreglo con dos elementos, cada uno un arreglo


a su vez. Para acceder a los elementos del arreglo, se especifican dos
índices, uno para el arreglo y uno para el elemento dentro del arreglo. En
el ejemplo, se accede al tercer elemento (2) en el segundo arreglo (1) de
la variable misNumeros, mientras que, desde el punto de vista de filas y
columnas, dicho elemento corresponde a la intersección de la segunda fila
y tercera columna:
Programación II 43

public class ClaseArreglo {


public static void main(String[] args) {
int [ ] [ ] misNumeros = { {1, 2, 3, 4}, {5, 6, 7, 8} };//Arreglo en
dos dimensiones
int x = misNumeros[1][2];//x guarda el valor ubicado en la posición
1-2
System.out.println(x); // Presenta el valor o elemento 7
correspondiente a 1-2
}
}

5.14. Definición de un arreglo bidimensional


Los autores Wanumen, Rivas y Mosquera (2017), mencionan que
un arreglo bidemensional, almacena datos en forma de columna o fila.
Por tanto, frecuentemente denominado arreglo de arreglo, almacena
datos en forma de columnas o filas multiples, en este orden, los datos
son almacenados en la memoria en forma de matriz. Par inicializarlo, se
adiciona una coma dentro de un par de corchetes [,] que sigue la definición
de arreglo. A continuación, se muestra la sintaxis del mismo:

Sintaxis:

Tipo [ ] [ ] nombre = new Tipo[cant1] [cant2];

Dónde:
Tipo: es uno de los tipos de datos (como int o String o el nombre de
una clase)

[ ] [ ] : Indica que se trata de un arreglo bidimensional

nombre: identificador del arreglo o variable

new: especifica un nuevo objeto


44 Ángel Pérez

cant1: especifica la cantidad máxima de elementos del primer arreglo o


fila; puede ser una variable o una constante

cant2: especifica la cantidad máxima de elementos del segundo arreglo


o columna; puede ser una variable o una constante

La declaración anterior, permite una serie de variantes a tomar en


cuenta, a la hora de definir un arreglo bidimensional:

int [ ] [ ] notas = new int[10][10]; //El arreglo notas tiene 100 elementos
tipo int
int num = 10;

int [ ] [ ] notas = new int[num][num];// igual que en el caso anterior

int num = entrada.nextInt();

int [ ] [ ] notas = new int[num][num];// cantidad de elementos es


indicada por el usuario

5.15. Recorrido de un arreglo bidimensional


Para Schildt (2017), estos se pueden usar dos instrucciones del tipo for,
anidadas (una dentro de otra) para recorrer los elementos de un arreglo
bidimensional (para lo cual se requieren dos índices, uno para recorrer las
filas y otro para recorrer las columnas):

public class ArregloNum {

public static void main(String[] args) {

int[ ] [ ] misNumeros = { {1, 2, 3, 4}, {5, 6, 7} };


Programación II 45

for (int i = 0; i < misNumeros.length; ++i) { //recorrido de filas


for(int j = 0; j < misNumeros[i].length; ++j) { //recorrido de
columnas
System.out.println(misNumeros[i][j]); // impresión del elemento
[i][j]
}
}
}
}

6. OPERADORES (ARITMÉTICOS, RELACIONALES Y LÓGICOS) Y


EXPRESIONES
Al respecto, Schildt (2017), refiere que los operadores son símbolos
que permiten relacionar uno o dos datos (pueden ser constantes, variables
o combinación), llamados operandos. Esta combinación, recibe el nombre
de expresión, la cual se evalúa (se dan valores concretos a cada operando)
para obtener un resultado. Según el tipo de resultado, se tienen los
siguientes tipos de expresiones:

• Si el resultado es numérico, la expresión de la cual proviene es


aritmética

• Si el resultado es lógico, la expresión de la cual proviene es lógica

A continuación, se presentan los distintos tipos de operadores y


ejemplos de implementación en lenguaje Java.

6.1. Operadores aritméticos


El primer tipo de operador es el aritmético, los cuales según Deitel y
Deitel (2012), son considerados los operadores binarios ya que funcionan
con dos operandos. En este orden, las expresiones de Java deben escribirse
en formato de línea recta para facilitar la escritura de programas
46 Ángel Pérez

en la computadora. Además, realizan la misma función que en la vida


real porque se utilizan para calcular variadas funciones matemáticas. Los
mismos se presentan el siguiente resumen:

Cuadro 3

Operadores aritméticos

Operador Nombre Descripción Ejemplo


+ Adición Suma dos valores x+y
- Substracción Substrae un valor de otro x-y
* Multiplicación Multiplica dos valores x*y
/ División Divide un valor por otro x/y
% Módulo Retorna el resto de la división x%y
++ Incremento Incrementa el valor de una ++x
variable en 1
-- Decremento Decrementa el valor de una --x
variable en 1
Fuente: Schildt (2017)

A continuación, se presenta un ejemplo en Java que involucra las cuatro


operaciones aritméticas básicas.

// Ejemplo de operadores aritméticos básicos


class MatBasica {
public static void main(String[] args) {

//Aritmética usando datos tipo entero

System.out.println(“Aritmética de números enteros”);

int a = 1 + 1;

int b = a * 3;
Programación II 47

int c = b / 4;
int d = c - a;
int e = -d;
System.out.println(“a = ” + a);
System.out.println(“b = ” + b);
System.out.println(“c = ” + c);
System.out.println(“d = ” + d);
System.out.println(“e = ” + e);
}
}
El ejemplo anterior, involucra la creación de expresiones aritméticas,
cuyo resultado es numérico (tipo entero, en todos los casos arriba
mostrados). El operador módulo, %, retorna el resto de la división. Puede
ser aplicado a datos tipo punto flotante, también como a los datos de tipo
entero, como se muestra en el siguiente ejemplo.
class Modulo {
public static void main(String[] args) {
//Manejo del operador %
int x = 42;
double y = 42.25;
System.out.println(“x mod 10 = ” + x % 10);
System.out.println(“y mod 10 = ” + y % 10);
}
}

6.2. Operadores relacionales


Schildt (2017), menciona que los operadores relacionales se consideran
una condición es una expresión que puede ser verdadera o falsa, luego
de ser evaluada. La misma es la base de la instrucción if, para tomar
decisiones. Las condiciones se pueden formar a través del uso de
operadores de comparación (operadores relacionales), presentados en el
siguiente cuadro.
48 Ángel Pérez

De esto, el mismo autor referido con anterioridad, acota que todos los
operadores relacionales comparten lo siguiente: son binarios (involucran
dos operandos, que pueden ser numéricos, caracteres, texto o cadena),
tienen el mismo nivel de precedencia y se asocian de izquierda a derecha.
Los operadores relacionales determinan la relación que un operando tiene
con el otro. Específicamente, determinan la igualdad y el orden (mayor que,
menor que…). Los operadores relacionales se muestran a continuación:

Cuadro 4

Operadores Relacionales

Operador Nombre Ejemplo


== Igual que x == y
!= Distinto de x != y
> Mayor que x>y
< Menor que x<y
>= Mayor o igual que x >= y
<= Menor o igual que x <= y
Fuente: Schildt (2017)

Al respecto, el resultado de aplicar un operador relacional es de


tipo lógico o booleano. Los operadores relacionales se usan con mayor
frecuencia en las expresiones que controlan la declaración if (condiciones o
decisiones) y las diversas declaraciones de bucle o repetición (condiciones
de control de repetición).
Aunado a esta idea, cualquier tipo en Java, incluidos los números
enteros, los números de coma flotante, los caracteres y los booleanos
se pueden comparar utilizando la prueba de igualdad (operador ==; un
solo signo igual representa al operador de asignación), y la prueba de
desigualdad (operador !=). Solo los tipos numéricos se pueden comparar
usando los operadores de ordenamiento. Es decir, solo se pueden comparar
operandos enteros, de coma flotante y de caracteres para ver cuál es
mayor o menor que el otro. Como se indicó, el resultado producido por un
operador relacional es un valor booleano (verdadero o falso). Por ejemplo,
el siguiente fragmento de código es perfectamente válido:
int a = 4;
int b = 1;
Programación II 49

boolean c = a < b;
En este caso, el resultado de a < b (el cual es falso) se almacena en c.

6.3. Operadores lógicos


Según criterios de Deitel y Deitel (2012), este tipo de operadores puede
aplicarse sobre operandos de tipo lógico (variables o constantes, cuyo
valor es falso o verdadero). La combinación de operadores relacionales y
operador lógico permite formular condiciones (o preguntas) más complejas.
Para Arroyo (2019), los operadores lógicos trabajan con datos o
expresiones booleanas que pueden ser verdaderas o falsas. Generalmente,
los dos primeros, es decir, la conjunción y disyunción son binarios, mientras
que la negación es unario. Así, los resultados de estos se presentan en
las tablas de verdad que contienen todas las posibles combinaciones de
valores involucrados en una operación lógica.

Cuadro 5
Operadores lógicos

Operador Nombre Descripción Ejemplo


Retorna true si ambos
&, && and operandos (expresiones) x < 5 && x < 10
son verdaderos
Retorna true si uno
o ambos operandos
|, || or x < 5 || x < 4
(expresiones) son
verdaderos
Invierte el resultado de la !(x < 5 && x <
! not
expresión 10)
Retorna false si ambos
xor (or
^ operandos (expresiones) x<5^x<4
exclusivo)
son verdaderos o falsos
Fuente: Deitel y Deitel (2012)
50 Ángel Pérez

Como se indicó anteriormente, el resultado de aplicar un operador


lógico es de tipo lógico. Esta aseveración es ilustrada con el siguiente
ejemplo en Java.
// Ejemplo de manejo de operadores lógicos
class OpLogico {
public static void main(String[] args) {
boolean a = true;
boolean b = true;
boolean c = a | b;
boolean d = a & b;
boolean e = a ^ b;
boolean f = (!a & b) | (a & !b);
boolean g = !a;
c %= 6;
System.out.println(“ a = ” + a);
System.out.println(“ b = ” + b);
System.out.println(“ a | b = ” + c);
System.out.println(“ a & b = ” + d);
System.out.println(“ a ^ b = ” + e);
System.out.println(“!a & b | a & !b = ” + f);
System.out.println(“ !a = ” + f);
}
}

6.4. Operadores de asignación


Para Deitel y Deitel (2012), un operador de asignación se utiliza para
modificar el valor de cualquier variable, este operador se expresa con el
signo de igual (=), además, se puede susar con cualquiera de los tipos de
datos descritos en las secciones previas.
Programación II 51

Cuadro 6

Operadores de asignación

Operador Ejemplo Igual a


= x=5 x=5
+= x += 3 x=x+3
-= x -= 3 x=x-3
*= x *= 3 x=x*3
/= x /= 3 x=x/3
%= x %= 3 x=x%3
&= x &= 3 x=x&3
|= x |= 3 x=x|3
^= x ^= 3 x=x^3
>>= x >>= 3 x = x >> 3
<<= x <<= 3 x = x << 3
Fuente: Wu (2001)

La tabla anterior muestra que hay operadores de asignación compuesta


para todos los operadores binarios aritméticos. Por lo tanto, cualquier
declaración de la forma:

var = var operador expresión;

Puede ser reescrita en la forma:

var operador= expresión;

En todos los casos, expresión es de tipo aritmético.

// Ejemplo de operadores de asignación


class OpAsigna {
public static void main(String[] args) {
52 Ángel Pérez

int a = 1;
int b = 2;
int c = 3;
a += 5;
b *= 4;
c += a * b;
c %= 6;
System.out.println(“a = ” + a);
System.out.println(“b = ” + b);
System.out.println(“c = ” + c);
}
}

7. SENTENCIAS DE DECISIÓN Y DE CICLO


Para Eck (2019), la capacidad de una computadora para realizar
tareas complejas se basa en sólo unas pocas formas de combinar simples
instrucciones en estructuras de control. En Java, sólo hay seis de estas
estructuras que se usan para determinar el flujo normal de control en un
programa y, de hecho, sólo tres de ellos serían suficientes para escribir
programas que realicen cualquier tarea. Las seis estructuras de control
son: el bloque, el bucle while, el bucle do... while, el bucle for, la sentencia
if, y la instrucción switch. Cada una de estas estructuras se considera una
única “declaración”, pero una declaración estructurada que puede contener
una o más instrucciones dentro de sí misma.

7.1. Instrucciones de decisión


En este orden, Eck (2019), la instrucción if se usa para especificar un
bloque de código que se ejecutará, si una condición es verdadera (decisión).
Sintaxis:
if (condición) { // Bloque de código a ejecutar si la condición es
verdadera}
Programación II 53

La palabra if está en minúsculas. Las letras mayúsculas (If o IF)


generarán un error. En el siguiente ejemplo, se prueban dos valores para
determinar si 20 es mayor que 18. Si la condición es verdadera, se imprime
un mensaje de texto:

int x = 20;
int y = 18;
if (x > y) {
System.out.println(“x es mayor que y”);
}

7.2. Instrucción if-else


Al respecto, Arroyo (2019), menciona que la estructura if-else se usa
para la toma de decisiones sobre un valor preexistente. En tal sentido, si
una condición basada en ese valor preexistente se cumple (si esa condición
es verdadera), el programa tomará un camino, por el contrario, si es falsa,
éste tomará uno diferente.
La instrucción anterior, sólo contempla qué hacer, en caso que la
condición sea verdadera; si ésta no lo es, la acción continúa con la siguiente
instrucción en el programa. La instrucción if-else contempla qué hacer, en
caso que la condición no sea verdadera, a través de la cláusula else (de lo
contrario).

Sintaxis:

if (condición) {
// Bloque de código a ser ejecutado si la condición es verdadera
} else {
// Bloque de código a ser ejecutado si la condición es falsa
}

En el siguiente ejemplo, dependiendo del valor de la variable hora, se


mostrará el saludo “Buen día.” o “Buena noche.”.
54 Ángel Pérez

7.3. Instrucción else-if


Para Arroyo (2019), puede existir una situación en la que se examinen
dos o más condiciones; como se vió en la instrucción anterior, las acciones
a ejecutar, en caso que la primera condición fuese verdadera o no,
quedaron cubiertas por la instrucción if-else. Se infiere entonces, en caso
que haya una segunda condición (o más), la instrucción else-if (instrucción
if anidada) permite especificar una nueva condición, si la primera es falsa.
Esta instrucción presenta la siguiente sintaxis:

if (condición1) {
// Bloque de código a ser ejecutado si la condición1 es verdadera
} else if (condición2) {
// Bloque de código a ser ejecutado si la condición1 es falsa y
condición2 es verdadera
} else {
// Bloque de código a ser ejecutado si la condición1 es falsa y
condición2 es falsa
}

En el siguiente ejemplo, dependiendo del valor de la variable hora, se


mostrará el saludo “Buen día.”, “Buena tarde.” o “Buena noche.”.

int hora = 22;


if (hora < 10) {
System.out.println(“Buen dia.”);
} else if (hora < 20) {
System.out.println(“Buena tarde.”);
} else {
System.out.println(“Buena noche.”);
}
// Presenta “Buena noche.”
Programación II 55

7.4 Instrucción if-else abreviado (operador ternario)


En criterio de Arroyo (2019), el operador tenario es aquel que devuelve
un valor, seleccionado entre dos operadores posibles. En este orden, esta
selección dependerá de la evaluación de una expresión relacional y lógica,
además, ésta puede tomar dos valores: verdadero o falso, quedando como
sintaxis:

expr? valor1: valor2

Hay una variante de la instrucción if-else que se escribe en una sola


línea, la cual se conoce como if-else abreviado u operador ternario, cuya
sintaxis es como sigue:

variable = (condición) ? expresiónVerdadera : expresiónFalsa;

En vez de escribir:

int hora = 20;


if (hora < 18) {
System.out.println(“Buen día.”);
} else {
System.out.println(“Buena noche.”);
}
Se puede escribir:
int hora = 20;
String result = (hora < 18) ? “Buen dia” : “Buena noche.”;
System.out.println(result);// Nueva instrucción, para imprimir el
resultado

7.5. Instrucción switch (según sea)


En criterio de Arrollo (2019), acota que con el switch se puede hacer
un control del tipo if else if… más estructurado, pero en realidad no
56 Ángel Pérez

exactamente igual puesto que con el switch, lo que se hace es definir un


conjunto de casos que van a tener una ejecución distinta y se ejecutará el
caso que coincida con el valor indicado en el mismo.
Continuando con la idea, esta instrucción sirve para especificar varios
bloques de código alternativos a ser ejecutados, es decir, es una instrucción
de selección múltiple. La condición <según sea> es una expresión
aritmética y la sintaxis general es como sigue:

switch(expresión) {
case x:
// Bloque de código
break;
case y:
// Bloque de código
break;
default:
// Bloque de código
}
La expresión <según sea> se evalúa y el valor de la misma se compara
con el valor de cada uno de los casos. Si hay coincidencia, se ejecuta
el bloque de código asociado. La instrucción break sirve para salir de
la instrucción switch, una vez se ha ejecutado dicho código. Si no hay
coincidencia, se ejecuta el bloque de código asociado a default.
El siguiente ejemplo muestra la selección de un día de la semana,
basado en el valor contenido en la variable día.
int dia = 4;
switch (dia) {
case 1: System.out.println(“Lunes”);
break;
case 2: System.out.println(“Martes”);
break;
case 3: System.out.println(“Miércoles”);
Programación II 57

break;
case 4: System.out.println(“Jueves”);
break;
case 5: System.out.println(“Viernes”);
break;
case 6: System.out.println(“Sábado”);
break;
case 7: System.out.println(“Domingo”);
break;
}
// Imprime “Jueves” (día 4)

7.6. CICLO – REPETICIÓN – ITERACIÓN


En este punto, se puede referir que una instrucción iterativa o repetitiva,
también conocida como bucle, tiene la misión de ejecutar las mismas
instrucciones de código una y otra vez, mientras que se cumpla una
determinada condición. Según Aguirre (2020), se llama ciclo a la secuencia
de sentencias que se repiten en un bucle. En Java hay tres tipos diferentes
de bucles: for, while y do-while.

7.6.1. Instrucción while


Aguirre (2020), acota que los ciclos while en programación Java van a
permitir, como su nombre indica (cuyo significado es “mientras”), repetir
una acción en un bucle siempre y cuando se cumpla una condición booleana
de control. En este orden, se puede inferir que un ciclo o instrucción while
repetirá un conjunto de acciones (o instrucciones) una y otra vez, mientras
una condición de control de ejecución del lazo o bucle permanezca
verdadera. En este caso, la sintaxis de la instrucción:

while (condición) {
// Bloque de código
}
58 Ángel Pérez

En este orden, la instrucción while ejecuta el bloque de código,


mientras la condición sea verdadera. Para ilustrar lo indicado, se muestra
a continuación un ejemplo:

int i = 0; // valor inicial de la variable de control del lazo o bucle de


repetición
while (i < 5) {// condición de control de la ejecución del lazo o bucle
de repetición
System.out.println(i);
i++;// esta instrucción asegura que la condición sea falsa en algún
momento
}

El bloque de código debe incluir la(s) instrucción(es) necesaria(s)


para que la condición de control sea falsa en algún punto del proceso,
deteniendo la ejecución de la instrucción (en el ejemplo: i++).

7.6.2. Instrucción do-while


Como indica Wu (2001), la instrucción do-while se define como un
bucle o lazo preprueba, porque la condición se verifica antes de ejecutar
el bloque de instrucciones. Debido a que la condición se verifica primero,
se puede dar el caso en que el bloque de instrucciones no se ejecute (la
condición es falsa). Esta instrucción ejecuta el bloque de código una vez,
antes de verificar si la condición es verdadera, luego repetirá el proceso
mientras dicha condición sea verdadera. La sintaxis correspondiente:

do {
// Bloque de código
}
while (condición);

El ejemplo anterior puede ser reescrito, usando la instrucción do-while


de la siguiente manera:
Programación II 59

int i = 0; // valor inicial de la variable de control del lazo o bucle de


repetición
do {
System.out.println(i);
i++;// esta instrucción asegura que la condición sea falsa en algún
momento
} while (i < 5); // condición de control de la ejecución del lazo o bucle
de repetición

Al igual que en la instrucción while el bloque de código debe


incluir la(s) instrucción(es) necesaria(s) para que la condición sea falsa en
algún punto del proceso, deteniendo la ejecución de la instrucción (en el
ejemplo: i++).

7.6.3. Instrucción for


Por su parte, Arroyo (2019), menciona que el ciclo for es similar a while,
pero, además, de la condición, incluye la inicialización de una variable y
un incremento o decremento de esa variable. Se puede afirmar que en un
principio no es necesario que incluya estas tres partes por lo que se puede
incrementar varias veces.
Bajo esta perspectiva, se puede argumentar de acuerdo a la idea anterior
que es la tercera de las instrucciones de repetición, pero a diferencia de
las anteriores, ejecuta un bloque de código un número determinado de
veces. Para realizarlo, se apoya en una variable de control y se muestra el
siguiente formato o sintaxis:

for (instrucción 1; instrucción 2; instrucción 3) {


// Bloque de código
}

Esta instrucción funciona de la siguiente manera. Cuando el bucle o


lazo comienza, la instrucción 1 (inicialización, se establece el valor de la
variable de control) se ejecuta una sola vez. A continuación, se evalúa la
condición (instrucción 2), que debe ser una expresión booleana, la cual
compara la variable de control contra un valor objetivo. Si esta expresión
60 Ángel Pérez

es verdadera, entonces el cuerpo del bucle se ejecuta. Si es falso, el bucle


termina.
A continuación, la parte de iteración del bucle (instrucción 3) se ejecuta.
Esta es usualmente una expresión que incrementa o disminuye la variable
de control del bucle. El bucle se repite, evaluando primero la condición,
luego ejecutando el cuerpo del bucle, y luego ejecutando la iteración con
cada pasada. Este proceso se repite hasta que la condición deja de ser
verdadera. A continuación, se muestra un extracto de código que ilustra el
funcionamiento descrito:

for (int i = 0; i < 5; i++) {//La variable i inicia en 0, se compara con 5


e incrementa en 1.
System.out.println(i);//El valor de i es mostrado
}// Fin del lazo; el proceso de comparación con 5 e incremento se
ejecuta de nuevo

7.6.4. Instrucción for-each


Para Arroyo (2019), acota que con este ciclo es posible recorrer
estructuras más complejas, como listas determinadas colecciones En este
orden, a estas estructuras se les conocen como iterables, ya que tienen un
mensaje del tipo iterator que devolverá la instancia del iterator, facilitando
el recorrido de objetos existentes en una colección sin necesidad de definir
el numero de elementos a recorrer.
Aunado a la idea anterior, se puede afirmar que es una variante que se
usa exclusivamente para manejar los elementos de un arreglo, en donde
el tratamiento (o recorrido) de los mismos es estrictamente secuencial,
por lo que no se requiere una instrucción de paso o incremento, ni una
condición de finalización, por lo que su estructura es mucho más simple
que la instrucción for, como muestra la sintaxis:

for (tipo variable : NombreArreglo) {


// Bloque de código
}

El siguiente ejemplo es un extracto de código que presenta todos los


elementos contenidos en un arreglo cars, usando una instrucción “for-
each”:
Programación II 61

String[] carros = {“Volvo”, “BMW”, “Ford”, “Mazda”};


for (String i : carros) {// El tipo de dato de la variable i debe coincidir
con el del arreglo
System.out.println(i);
}

8. MANEJO DE STRING
Como indica Eck (2019), un dato de tipo String es una secuencia de
caracteres, por ejemplo: “¡Hola Mundo!”. Las comillas dobles son parte del
dato (o literal); tienen que ser tecleadas en el programa. Sin embargo, no
son parte del valor real de la cadena, que consiste sólo en caracteres entre
las comillas. Una cadena puede contener cualquier número de caracteres,
incluso ninguno. Una cadena sin caracteres se llama la cadena vacía y se
representa por “” (un par de comillas dobles sin nada entre ellas). Las
comillas simples se usan para los caracteres y las comillas dobles para las
cadenas.

String saludo = “Hola”;

En el ejemplo anterior, se ha definido saludo como una variable tipo


cadena (String) y se ha inicializado con el valor (o literal) “Hola”. Luego, la
clase String permite representar cadenas de caracteres. Todos los literales
de tipo cadena, como “abc”, se implementan como instancias de esta clase,
es decir, como objetos. Las cadenas son constantes; sus valores no pueden
ser cambiados después de su creación. Debido a que los objetos String son
inmutables, pueden ser compartidos. Por ejemplo:

String cad = “abc”;

es equivalente a:

char dato[] = {‘a’, ‘b’, ‘c’};


String cad = new String(dato);
62 Ángel Pérez

Una operación que se puede realizar es la unión o concatenación de


cadenas, la cual se lleva a cabo usando el operador “+”:

System.out.println(“abc”);
String cde = “cde”;
System.out.println(“abc” + cde); //Produce como salida “abccde”.

La clase String provee una serie de operaciones (métodos) que se


pueden realizar sobre cadenas.

Longitud de una cadena:

String txt = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”;


int len = txt.length();
System.out.println(“La longitud de la cadena txt es: “ + len);//len = 26

Mayúscula y minúscula:

String txt = “ Hola Mundo “;


System.out.println(txt.toUpperCase()); // Muestra en pantalla: “ HOLA
MUNDO “
System.out.println(txt.toLowerCase()); // Muestra en pantalla: “ hola
mundo “

Para hallar una cadena dentro de otra, se usa el método indexOf(),


que devuelve el índice (posición) de la primera ocurrencia de un texto
específico en una cadena:

String txt = “Por favor, ubique donde ‘ubique’ ocurra!”;


System.out.println(txt.indexOf(“ubique”)); // Devuelve 11

Uso del método concat() para concatenar cadenas:


Programación II 63

String nombre = “Juan “;


String apellido = “Perez”;
System.out.println(nombre.concat(apellido)); //Produce “Juan Perez”
como resultado.

El uso del operador + en números y cadenas produce resultados


distintos, según los datos que se estén combinando.

Sumando dos valores numéricos:

int x = 10;
int y = 20;
int z = x + y; // z será igual a 30 (un número entero)

Sumando dos cadenas:

String x = “10”;
String y = “20”;
String z = x + y; // z será igual a 1020 (una cadena)

Sumando un valor numérico y una cadena:

String x = “10”;
int y = 20;
String z = x + y; // z será igual a 1020 (una cadena)

9. COMPILACIÓN DE PROGRAMAS
Según Aguirre (2020), en la compilación de programas se analiza todo
el programa fuente, se crea el programa objeto y luego va a permitir la
64 Ángel Pérez

ejecución del mismo. Es decir, tras compilar un programa su ejecución es


mucho más rápida que la de uno interpretado. En este orden, visto en
detalle, la escritura de un programa en Java pasa por cinco fases, como
indican Deitel y Deitel (2012), las cuales son: edición, compilación, carga,
verificación y ejecución.

9.1. Edición
Al respecto, Deitel y Deitel (2012), mencionan que esta fase corresponde
a la escritura y grabado del programa en sí. Como herramienta, se puede
usar algo tan sencillo como Notepad (de Windows), pasando por editores
más elaborados como TextPad, Eclipse, NetBeans, entre otros (también
conocidos como ambiente integrado de desarrollo, o AID, capaces de llevar
a cabo las fases aquí descritas desde un solo sitio, algunas de manera
transparente). El resultado, conocido como programa fuente, debe ser
guardado en un archivo con extensión .java, como muestra el siguiente
ejemplo de programa:

/*
Este es un ejemplo de programa en Java.
Se debe guardar en disco como “Ejemplo.java”.
*/
class Ejemplo {
// El programa comienza con una llamada al método main().
public static void main() {
System.out.println(“Este es un muy simple programa en
Java.”)
}
}

9.2. Compilación
Para Deitel y Deitel (2012), en esta fase se realiza una revisión del
código fuente desarrollado en la fase anterior y, en caso que no hayan
errores de sintaxis, dicho código es traducido a código binario (bytecode,
en inglés) y se genera un archivo que lleva el mismo nombre que el archivo
Programación II 65

fuente, pero la extensión es .class (continuando el caso comenzado en la


fase anterior, se genera el archivo Ejemplo.class).
Se infiere entonces, de acuerdo con la idea anterior, dicha traducción
genera el conjunto de tareas a ser llevadas a cabo en la fase de ejecución.
Así, el proceso de compilación se lleva a cabo a través de la ejecución
del comando javac, de la siguiente forma (en la línea de comandos en
Windows):
javac <ruta o directorio><nombre_programa>

9.3. Carga del programa en memoria


Según Deitel y Deitel (2012), cuando se ejecuta el programa en java
con el comando java, entra en juego un componente llamado cargador de
clases. Este, carga las clases dinámicamente en la JVM. Cabe recordar que
la JVM es como una especie de computadora virtual. Lo primero que hará
el cargador de clases es intentar cargar el editado.
A diferencia de las anteriores fases, ésta y las subsiguientes están
asociadas a la ejecución, desde la línea de comandos, de la siguiente
orden:

java <ruta o directorio><nombre_programa>

Al momento de la ejecución del comando anterior, interviene la Máquina


Virtual de Java (o JVM, por sus siglas en inglés). Este es una aplicación
especial que emula a un computador, aislando al programa a ejecutar
del sistema operativo y el hardware en el cual se encuentra alojado. El
programa a ejecutar es ubicado en memoria principal (con los recursos
que necesite) por el cargador de clases (class loader, en inglés).

9.4. Verificación y ejecución del código binario


De acuerdo con la teoría de Deitel y Deitel (2012), estos autores
afirman que a fin de asegurar que no se violan restricciones de seguridad,
a medida que se van cargando las clases en memoria, el verificador de
bytecodes examina los bytecodes para asegurar que sean válidos. Esto se
realiza porque el sistema Java run-time no confía en el código que se está
cargando.
Aunado a esta idea, se realizan pruebas tales como que el formato del
66 Ángel Pérez

fragmento de código sea el correcto, así como también que no se forjen


punteros, no haya violaciones de acceso a la memoria y los objetos sean
accedidos por lo que son (Por ejemplo, que un objeto System siempre sea
usado como System y nada más).
Se infiere entonces, luego que las clases son cargadas, verifica que los
códigos binarios son válidos y no violan las restricciones de seguridad de
Java. Este proceso permite proteger al sistema operativo o a los archivos
de cualquier amenaza externa (como virus, por ejemplo). Si el proceso
de verificación se cumple, la máquina virtual ejecuta los códigos binarios,
llevando a cabo las acciones especificadas por el programa.
Nota: las fases de compilación en adelante se llevarán a cabo, siempre
que se encuentre instalado el kit de desarrollo de Java (jdk, por sus siglas
en inglés). Dicha instalación da como resultado una carpeta o directorio
de programas, entre los cuales se encuentran javac y java, encargados
de las fases de compilación hasta la ejecución del programa en memoria
principal.
La versión más actualizada y su documentación correspondiente puede
ser descargada en el portal de Oracle (www.oracle.com/technetwork/
java/), actualmente, esta es la compañía que posee los derechos sobre
Java.
ACTIVIDADES DE AUTOEVALUACIÓN

Nombre: _______________________________
UNIVERSIDAD
Privada
DR. RAFAEL BELLOSO CHACÍN Cédula: __________________ Sección: ______

Responda los siguientes enunciados. Valor: 1 pts

1. En Java, un ________________ es oficialmente una unidad de


___________.

2. Si Ejemplo es una clase en Java, indique el nombre con el cual se


debe guardar en archivo.

3. La definición de una clase en Java debe comenzar con la palabra


clave:________.

4. Sea texto una variable tipo cadena (o string), que contiene la frase
“Hola Maracaibo”. Escriba las instrucciones necesarias para:

a. Definir la variable e inicializarla con el valor indicado anteriormente.

b. Escribir la(s) instrucción(es) para presentar la conversión del


contenido de texto en mayúsculas.

c. Escribir la(s) instrucción(es) para presentar la conversión del


contenido de texto en minúsculas.

5. Revise y modifique el siguiente programa:

/*
Este es un ejemplo de programa en Java.
Se debe guardar en disco como “Ejemplo.java”.
/
clase Ejemplo {
El programa comienza con una llamada al método main().
public void main() {
System.println(“Este es un muy simple programa en Java.”
}
}

6. Los términos public static void son palabras reservadas que se


usan para ________________________________.

7. En la jerarquía de datos, ¿cuál es el elemento menos complejo?

8. Sea y una variable tipa decimal flotante (float) con valor igual a
7.0 Si dicha variable pertenece a una clase llamada MiClase, elabore un
programa que
UNIDAD II
CLASES, MÉTODOS Y HERENCIA
70 Ángel Pérez

UNIDAD II
CLASES, MÉTODOS Y HERENCIA

1. ABSTRACCIÓN, ENCAPSULADO Y POLIMORFISMO


Para Jaime (2002), para realizar de forma eficiente un análisis, hacer un
diseño e incluso si se trata de comprender claramente el funcionamiento
de cualquier sistema, es necesario determinar su estructura, con el fin
de establecer lo que es realmente significativo. En este sentido, los
lenguajes orientados a objetos, utilizan éstos y sus relaciones como base
para el desarrollo de la lógica de programación. Un lenguaje orientado a
objetos presenta, entre otras, las siguientes características: abstracción,
encapsulado y polimorfismo, los cuales son aspectos relevantes para el
diseño e implantación de sistemas informáticos.

1.1. Abstracción
Para Lewis y Chase (2006), un objeto es una abstracción, lo que quiere
decir que los detalles precisos de cómo funciona el objeto son irrelevantes
desde el punto de vista del usuario del objeto. Un elemento esencial de
la programación orientada a objetos es la abstracción, ya que permite
el manejo de la complejidad. Por ejemplo, la gente no piensa en un
vehículo como un conjunto de miles de piezas individuales, que actúan de
manera sincronizada, sino que es un objeto bien definido con su propio
comportamiento único.
Aunado a lo anterior, esta abstracción permite a la gente usar un
vehículo para movilizarse a cualquier parte, sin sentirse abrumada por la
complejidad de las piezas individuales. Pueden ignorar los detalles de cómo
funcionan el motor, la transmisión y los sistemas de frenado. En cambio,
son libres de utilizar el vehículo (objeto) como un todo.
Una manera poderosa de manejar la abstracción, es a través del uso de
clasificaciones jerárquicas. Esto permite poner en capas la semántica de
sistemas complejos, dividiéndolos en piezas manejables. Desde el exterior,
el vehículo es un solo objeto. Una vez dentro, se ve que el vehículo consta
de varios subsistemas: dirección, frenos, sistema de sonido, cinturones de
seguridad, calefacción, teléfono móvil, entre otros.
A su vez, cada uno de estos subsistemas se compone de unidades
más especializadas. Por ejemplo, el sistema de sonido puede consistir en
Programación II 71

una radio, un reproductor de CD y/o un reproductor de MP3. El punto es


que se gestiona la complejidad del vehículo (o de cualquier otro sistema
complejo) a través del uso de abstracciones jerárquicas.
De lo anterior, se puede notar que, desde el punto de vista del usuario,
el objeto (vehículo, cuenta bancaria, etc.) presenta una serie de funciones
que permiten su manejo, pero los detalles precisos de cómo opera son
irrelevantes para el usuario. Luego, la abstracción permite al usuario el uso
de una serie de objetos, sin preocuparse sobre su estructura y composición
interna.

1.2. Encapsulación
Como se indicó en el punto 4, todo en Java está asociado a clases y
objetos, junto con sus atributos y métodos. Los atributos (o variables de
instancia) guardan los datos, mientras que los métodos permiten realizar
operaciones sobre dichos datos (como actualizar el valor de una variable,
por ejemplo). Es importante establecer, por protección de los datos, que
sólo el objeto pueda modificar sus variables, a través de sus propios
métodos. En palabras de Lewis y Chase (2006), un objeto debe ser auto-
gobernado; es decir, el objeto debe estar encapsulado dentro del sistema.
La encapsulación se logra a través del uso de modificadores de
visibilidad: public y private. Las variables de instancia se definen private,
con lo que se limita su visibilidad a los miembros (métodos) que se
encuentran dentro de la clase. La visibilidad de los métodos dependerá
del propósito de los mismos; los que permitan el acceso a los datos deben
ser declarados public. El siguiente ejemplo muestra el modificador private
aplicado al atributo de la clase Persona (nombre) y los métodos requeridos
para el manejo del mencionado atributo (conocidos con los prefijos get y
set), los cuales han sido definidos como public.

public class Persona {


private String nombre; // private = el acceso a la variable está
restringido

// Método Get; permite el acceso al valor guardado en la variable


nombre
public String getNombre() {
return nombre; // Devuelve el valor guardado en la variable nombre
72 Ángel Pérez

}
// Método Set; permite fijar el valor a guardar por la variable nombre
public void setNombre(String nuevoNombre) {
this.nombre = nuevoNombre;//El valor de nuevoNombre se asigna
a nombre
}
}

En el ejemplo anterior, se puede observar lo siguiente:

• El método get (getNombre()), a través de la instrucción return,


devuelve el valor del nombre de la variable.

• El método set (setNombre) toma un parámetro (nuevoNombre), cuyo


valor asigna a la variable nombre.

• Como la variable nombre es declarada privada, no se puede acceder


a ella desde fuera de esta clase.

Para verificar la última aseveración, se tiene la siguiente clase, en la


cual se ha creado un objeto tipo Persona y se intenta acceder al atributo
nombre:

public class MiClase {


public static void main(String[] args) {
Persona objetoPersona = new Persona();
objetoPersona.nombre = “Juan”;
System.out.println(objetoPersona.nombre);
}
}
Programación II 73

En este ejemplo, se está tratando de cambiar el valor del atributo


nombre, para luego imprimirlo, desde MiClase. Esto debe generar dos
mensajes de error, uno por cada acceso directo al atributo nombre, al
ejecutar el programa. A continuación, se presenta la versión corregida,
en donde se accede al atributo nombre a través de los métodos públicos
correspondientes

public class MiClase {


public static void main(String[] args) {
Persona objetoPersona = new Persona();
objetoPersona.setNombre(“Juan”);
System.out.println(objetoPersona.getNombre());
}
}

1.3. Polimorfismo
Polimorfismo (“muchas formas”), en opinión de Jaime (2002), es la
capacidad de un elemento para adoptar diferentes formas en tiempo de
ejecución, y ocurre cuando se tienen varias clases que se relacionan entre
sí por herencia. Esta permite a una clase obtener atributos y métodos de
otra clase. El polimorfismo utiliza estos métodos para realizar diferentes
tareas. Esto permite realizar una sola acción de diferentes maneras.
Para ilustrar lo dicho anteriormente, se tienen las clases: Animal, Cerdo
y Perro, en donde las dos últimas pertenecen (o heredan) a la primera.
Esto establece una relación de herencia, en donde Animal es la superclase
(o clase base) y Cerdo y Perro son subclases (o clases derivadas). Cada
animal emite su propio sonido, que queda registrado en el método llamado
sonidoAnimal(), presente en todas las clases.

class Animal {// Clase base o superclase


public void sonidoAnimal() {// Método en la superclase
System.out.println(“El animal emite un sonido”);
}
}
74 Ángel Pérez

class Cerdo extends Animal {// Clase derivada de Animal


// Método en la subclase con igual nombre al de la superclase
public void sonidoAnimal() {
System.out.println(“El cerdo gruñe: oinc, oinc”);
}
}
class Perro extends Animal {// Clase derivada de Animal
// Método en la subclase con igual nombre al de la superclase
public void sonidoAnimal() {
System.out.println(“El perro ladra: guau guau”);
}
}
class ClaseAnimal {//Clase principal
public static void main(String[] args) {//Creación de objetos
Animal objAnimal = new Animal(); // Crea un objeto Animal
Cerdo objCerdo = new Cerdo(); // Crea un objeto Cerdo
Perro objPerro = new Perro(); // Crea un objeto Perro

objAnimal.sonidoAnimal();//Salida: El animal emite un sonido


objCerdo.sonidoAnimal();//Salida: El cerdo gruñe: oinc, oinc
objPerro.sonidoAnimal();//Salida: El perro ladra: guau guau
}
}

A pesar que el nombre del método es el mismo en las tres clases, la


cualificación por objetos va a producir una salida distinta, correspondiente
al sonido a emitir por animal. La implementación permite agregar nuevas
clases, cada una correspondiendo a un animal distinto.
Programación II 75

2. CONTROL DE ACCESO A LOS MIEMBROS DE CLASES


Como se ha indicado, en Java, los atributos (o variables de instancia)
guardan los datos, mientras que los métodos permiten realizar operaciones
sobre dichos datos. Tanto los atributos como los métodos también son
conocidos como miembros de clase. La protección de los datos, como se
mencionó en el punto anterior, es un aspecto importante a implementar a
través del control de acceso a los miembros de clase.
En crterio de Schildt (2017), a través de la encapsulación, se puede
controlar cuáles miembros son accesibles y cuáles no, evitando el uso
indebido de los mismos. Si se permite el acceso a los datos sólo a través de
un conjunto de métodos bien definidos, se puede evitar el manejo directo
de los mismos. Así, cuando se implementa correctamente, una clase crea
una “caja negra” que puede ser utilizada, pero cuyo funcionamiento interno
no está abierto a la manipulación. Por ejemplo, se tiene la siguiente clase,
Stack.

// Esta clase define una pila de números enteros que puede almacenar
diez valores
class Stack {
int pila [ ] = new int[10];//Define un arreglo para ser usado como pila
int tope;//Controla la cantidad de elementos que hay en la pila.
// Inicializa el tope de la pila
Stack() {
tope = -1;//No hay elementos en la pila
}
}
// Inserta un valor en la pila
void inserta(int ítem) {
if(tope==9)
System.out.println(“La pila está llena.”);
else
pila[++tope] = ítem;//La variable tope se incrementa en 1
}
76 Ángel Pérez

// Extrae un valor de la pila


int extrae() {
if(tope < 0) {
System.out.println(“La pila no tiene elementos.”);
return 0;
}
else
return pila [tope--];//La variable tope se decrementa en 1
}
}
}

En el ejemplo anterior, los métodos inserta( ) y extrae() controlan los


datos almacenados en la pila. Como se observa, ambos métodos carecen
de una especificación de acceso definida (el método inserta() es tipo void,
por lo que no retorna valores, mientras que el método extrae() devuelve
un valor tipo int). La forma en que se puede acceder a un miembro está
determinada por el modificador de acceso adjunto a su declaración. Sierra
y Bates (2005), especifican que los modificadores de acceso de Java son
public, private y protected. El modificador protected se aplica sólo cuando
se trata de herencia. Los otros modificadores de acceso se describen a
continuación.
Cuando un miembro de una clase es declarado public, el mismo puede
ser accedido por cualquier otro código. Cuando un miembro de una clase
es especificado como private, entonces ese miembro sólo puede ser
accedido por otros miembros de su clase. Ahora, se puede entender por
qué main() siempre ha sido precedido por el modificador public: se llama
por código que está fuera del programa, es decir, por el sistema en tiempo
de ejecución de Java. Cuando no se utiliza ningún modificador de acceso,
entonces, por omisión, el miembro de una clase es público dentro de su
propio paquete, pero no puede ser accedido fuera de su paquete.
En las clases desarrolladas hasta ahora, todos los miembros de una
clase han utilizado el modo de acceso predeterminado. Por lo general,
se querrá restringir el acceso a los datos de los miembros de una clase,
permitiendo el acceso sólo a través de métodos. Un modificador de acceso
precede al resto de la especificación de tipo de un miembro. Es decir,
Programación II 77

debe comenzar la declaración de un miembro, como se muestra en los


siguientes ejemplos:

public int i;
private double j;
private int miMetodo(int a, char b){ // …

A continuación, se presenta una versión modificada de la clase Stack,


mostrada arriba:

// Esta clase define una pila de números enteros que puede almacenar
diez valores
class Stack {
/* Ahora, los atributos pilan y tope son privados (private). Esto
significa que no pueden
ser alterados, accidental o maliciosamente.
*/
private int pila[] = new int[10];
private int tope;
// Inicializa el tope de la pila
Stack() {
tope = -1;
}
}
// Inserta un valor en la pila
void inserta(int ítem) {
if(tope==9)
System.out.println(“La pila está llena.”);
else
pila[++tope] = ítem;
78 Ángel Pérez

}
// Extrae un valor de la pila
int extrae() {
if(tope < 0) {
System.out.println(“La pila no tiene elementos. “);
return 0;
}
else
return pila [tope--];
}
En el ejemplo, se observa que los atributos pila y tope (el índice que
señala la posición o tope de la pila) fueron especificados como private.
Esto significa que no pueden ser accesados o alterados, excepto a través
de inserta() y extrae().
La siguiente clase permite probar los cambios realizados a la clase
Stack:

class PruebaStack{
public static void main (String args [ ] ) {
Stack pila1 = new Stack(); //Primer objeto tipo Stack
Stack pila2 = new Stack();//Segundo objeto tipo Stack
// Se insertan valores en ambas pilas (10 valores en cada caso)
for (int i = 0; i<10; i++) pila1.inserta(i);
for (int i = 0; i<10; i++) pila2.inserta(i);
// Se extraen los valores insertados en cada pila
System.out.println(“Elementos en la pila1:”);
for (int i = 0; i<10; i++) System.out.println(pila1.extrae());
System.out.println(“Elementos en la pila2:”);
for (int i = 0; i<10; i++) System.out.println(pila2.extrae());
// Las siguientes instrucciones no son válidas
Programación II 79

// pila1.tope = -2;
// pila2.pila[3] = 100;
} //Fin del método main()
} // Fin de la clase

El ejemplo anterior debe ser ejecutado dos veces: la primera, tal como
se muestra el código y en la segunda, se deben eliminar las barras de
comentarios de las dos últimas instrucciones:

// pila1.tope = -2;
// pila2.pila[3] = 100;

Si se eliminan las barras de comentarios, se generarán errores en la


ejecución del programa, lo cual implica que los elementos tope y pila son
inaccesibles desde clases externas. Aunque los métodos normalmente
proporcionan acceso a los datos definidos por una clase, esto no siempre
tiene que ser así. Es apropiado permitir que una variable de instancia sea
pública cuando haya una buena razón para hacerlo.
Por ejemplo, la mayoría de las clases en este libro fueron creadas con
poca preocupación sobre el control de acceso a variables de instancia, en
aras de la simplicidad. Sin embargo, en la mayoría de las clases del mundo
real, se necesitará permitir operaciones sobre los datos sólo a través de
métodos.

3. PASO DE OBJETOS A MÉTODOS


En el ejemplo de la clase Animal, se usó el método sonidoAnimal(), que,
en todos los casos, solo posee una instrucción para presentar un mensaje.
En el ejemplo de la clase Stack, existen dos métodos: inserta(int ítem)
y extrae(), un poco más complejos que sonidoAnimal(). La definición de
cada método se presenta a continuación:
public void sonidoAnimal()
void inserta(int ítem)
int extrae()
80 Ángel Pérez

Como indica Pimienta, Aguilar, Ramírez y Gallegos (2014), el método


extrae() está precedido por la palabra int y su cuerpo de instrucciones
termina con la palabra return seguido de 0 (primera posibilidad), o seguido
de la variable pila (segunda posibilidad); es decir, este método debe
devolver (o retornar) un valor de tipo entero, como especifica la palabra
int.
En el caso de los métodos sonidoAnimal() e inserta(int ítem), el cuerpo
de instrucciones no incluye la palabra return, por lo que ambos métodos no
devuelven valores, como especifica la palabra void. El método inserta(int
ítem) incluye una variable de nombre item, de tipo entero, para especificar
el valor a ser insertado en la pila; este método puede ser usado (invocado
o llamado) de las siguientes formas:

pila1.inserta(i); // i debe ser una variable tipo int, con un valor


previamente definido
pila1.inserta(10); //10 va a ser insertado en el conjunto de valores
manejados por pila1

Es decir, se invoca el método y se pasa un argumento (variable o valor


constante), el cual es recibido en la definición del método a través del
parámetro correspondiente (ítem, en este ejemplo). El paso de argumentos
a métodos no está limitado a datos de tipo simple (o primitivo), sino que
también abarca el manejo de objetos, como se muestra en el siguiente
ejemplo:

// Ejemplo de paso de objetos a métodos


class Prueba {
int a, b; //Variables de clase o de instancia
Prueba (int i, int j) { //Constructor de la clase Prueba
a = i; //Inicialización de la variable de instancia a
b = j; //Inicialización de la variable de instancia b
}
// Retorna verdadero si el objeto o es igual al objeto que invoca
Boolean igualA (Prueba o) {
Programación II 81

if(o.a == a && o.b == b) return true;//Comparación de variables


else return false;
}
}
class PasaObj {
public static void main (String args [ ]) {
Prueba objeto1 = new Prueba(100, 22);//Creación de objeto1 tipo
Prueba
Prueba objeto2 = new Prueba(100, 22); //Creación de objeto2 tipo
Prueba
Prueba objeto3 = new Prueba(-1, -1); //Creación de objeto3 tipo
Prueba
System.out.println(“objeto1 == objeto2 ” + objeto1.igualA(objeto2));
System.out.println(“objeto1 == objeto3 ” + objeto1.igualA(objeto3));
}
}

Como se puede ver, el método igualA() dentro de la clase Prueba


compara si dos objetos son iguales y devuelve el resultado. Es decir,
compara el objeto que invoca con el que se pasa. Si contienen los mismos
valores, entonces el método devuelve verdadero (true). De lo contrario,
devuelve falso (false). El parámetro o en el método igualA() especifica
Prueba como su tipo de dato. Aunque Prueba es un tipo de clase creado
por el programa, se usa de la misma manera que los tipos de datos básicos
de Java.

4. RETORNO DE OBJETOS
Para Schildt (2017), un método puede devolver cualquier tipo de
datos, incluidos los de tipo clase que cree (es decir, objetos). Por ejemplo,
en el siguiente programa, el método sumaDiez() devuelve un objeto en el
que el valor de la variable a se incrementa en diez, comparado al objeto
que invoca.
82 Ángel Pérez

// Ejemplo de retorno de objetos


class Prueba {
int a; //Variable de instancia de la clase
Prueba (int i) {//Constructor de la clase Prueba
a = i; //Inicialización de la variable de instancia
}
Prueba sumaDiez () {
Prueba temp = new Prueba(a+10);//Creación del objeto temp
return temp;//El método devuelve el objeto temp, tipo Prueba
}
}
class RetObj {
public static void main (String args [ ]) {
Prueba obj1 = new Prueba(2);//Creación de obj1 como un objeto
tipo Prueba
Prueba obj2;//Creación de la variable de referencia obj2
//Inicialización de obj2 a través de la invocación a sumaDiez()
obj2 = obj1.sumaDiez ();
System.out.println(“obj1.a: ” + obj1.a); //Impresión del valor de
a en obj1
System.out.println(“obj2.a: ” + obj2.a); //Impresión del valor de
a en obj2
obj2 = obj2.sumaDiez ();
System.out.println(“obj2.a despues del segundo incremento: ” +
obj2.a);
}
}
Como se puede ver, cada vez que se invoca el método sumaDiez(), se
crea un nuevo objeto (tipo Prueba, como especifica definición del método)
y se devuelve una referencia a dicho objeto (return temp;) a la instrucción
de llamada. El programa anterior resalta otro punto importante: dado que
todos los objetos se asignan dinámicamente usando el operador new, los
objetos finalizan en el método en el cual fueron creados.
Programación II 83

5. SOBRECARGA DE MÉTODOS Y CONSTRUCTORES


En este punto se detalla la sobrecarga de métodos y constructores,
considerándose como técnica en Java en la que una clase puede tener
cualquier cantidad de constructores que difieran en la lista de parámetros,
en este orden de ideas, es relevante mencionar que los compiladores
diferencian estos constructores teniendo en cuenta el número de
parámetros en la lista y de su tipo.

5.1. Sobrecarga de Métodos


Existe la posibilidad de tener varios métodos compartiendo el mismo
nombre. Los métodos con igual nombre pueden ser declarados en la
misma clase, siempre y cuando tengan diferentes conjuntos de parámetros
(determinados por su número, tipo y orden); esta situación se conoce
como sobrecarga de métodos, de acuerdo a lo indicado por Schildt (2017).
Cuando se llama a un método sobrecargado, el compilador selecciona el
método apropiado examinando el número, tipo y el orden de los argumentos
en la llamada. La sobrecarga de métodos se utiliza comúnmente para
crear varios métodos que realizan las mismas o tareas similares, pero con
diferentes tipos o diferentes números de argumentos. Por ejemplo, los
métodos matemáticos abs, min y max (ubicados en la librería java.lang.
math), están sobrecargados con cuatro versiones cada uno:

1. Uno con dos parámetros tipo double.


2. Uno con dos parámetros tipo float.
3. Uno con dos parámetros tipo int.
4. Uno con dos parámetros tipo long.

Específicamente, el método max(), el cual, devuelve el mayor de dos


números, siguiendo la secuencia anterior, presenta la siguiente sintaxis:

public static double max(double a, double b)


public static float max(float a, float b)
public static int max(int a, int b)
public static long max(long a, long b)
84 Ángel Pérez

Los siguientes ejemplos muestran el uso de algunas versiones del


método max():
public class MaxEjemplo1
{
public static void main(String args[])
{
int x = 20;
int y = 50;
System.out.println(Math.max(x, y)); // Imprime el mayor de dos
valores tipo int
}
}
public class MaxEjemplo2
{
public static void main(String args[])
{
double x = 25.67;
double y = -38.67;
System.out.println(Math.max(x, y)); //Imprime el mayor de dos
valores tipo double
}
}

public class MaxEjemplo3


{
public static void main(String args[])
{
float x = -25.74f;
float y = -20.38f;
Programación II 85

System.out.println(Math.max(x, y)); //Imprime el mayor de dos valores


tipo float
}
}

Por separado, cada ejemplo muestra el uso del método max(), según
el tipo de datos de los valores numéricos de entrada. Para ilustrar la
sobrecarga en métodos, los ejemplos anteriores se pueden resumir en uno
solo como sigue a continuación:

public class MaxEjemplo


{
public static void main(String args[])
{
int xi = 20; //Definición de variables tipo int
int yi = 50;
double xd = 25.67; //Definición de variables tipo double
double yd = -38.67;
float xf = -25.74f; //Definición de variables tipo float
float yf = -20.38f;
// Imprime el máximo de dos valores tipo int
System.out.println(Math.max(xi, yi));
// Imprime el máximo de dos valores tipo double
System.out.println(Math.max(xd, yd));
//Imprime el máximo de dos valores tipo float
System.out.println(Math.max(xf, yf));
}
}
El ejemplo anterior muestra que una clase puede tener varios métodos
con el mismo nombre, cada uno de ellos con diferente firma (signature),
como indica Wu (2001), la firma de un método viene determinada por su
nombre, número y tipo de sus parámetros.
86 Ángel Pérez

5.2. Sobrecarga de constructores


Schildt (2017), acota que además de sobrecargar los métodos normales,
también se pueden sobrecargar los métodos del constructor de la clase.
De hecho, para la mayoría de las clases del mundo real, los constructores
sobrecargados son la norma, no la excepción. La siguiente es una clase
que ilustra lo señalado anteriormente:

class Caja {
double ancho;
double alto;
double profundidad;
// Constructor de la clase
Caja (double n, double l, double p) {
ancho = n;
alto = l;
profundidad = p;
}
// Calcular y retornar el volumen
double volumen () {
return ancho * alto * profundidad;
}
}

En el ejemplo anterior, se observa que el constructor de la clase requiere


tres parámetros (ancho, alto y profundidad), para calcular y retornar el
volumen. Esto significa que todas las declaraciones de objetos tipo Caja
deben pasar tres argumentos al constructor.

Luego, la siguiente declaración no es válida en la actualidad:

Caja objCaja = new Caja();


Programación II 87

Dado que el constructor del ejemplo anterior requiere tres parámetros,


es un error llamarlo sin ellos, por lo que la declaración anterior no es válida.
Esto plantea algunas cuestiones importantes. ¿Qué pasaría si simplemente
se quiere un objeto Caja, sin importar (o desconocer) cuáles eran sus
dimensiones iniciales? O, ¿qué pasa si se desea poder inicializar un cubo,
especificando un solo valor que se utilizaría para las tres dimensiones?
Como la clase Caja está actualmente escrita, estas otras opciones
no tienen respuesta. La solución a estos problemas es sobrecargar
el constructor de la clase para que maneje las situaciones descritas
anteriormente. Aquí hay un programa que contiene una versión mejorada
de la clase Caja que hace precisamente eso:

/* Se definen tres constructores para inicializar los atributos de la clase


de varias
formas
*/
class Caja {
double ancho;
double alto;
double profundidad;
// Constructor de la clase a usar cuando se especifican todas las
dimensiones
Caja (double n, double l, double p) {
ancho = n;
alto = l;
profundidad = p;
}
// Constructor de la clase a usar cuando no se especifican las
dimensiones
Caja ( ) {
ancho = -1; // -1 indica
alto = -1; // una caja
profundidad = -1; // no inicializada
88 Ángel Pérez

}
// Constructor de la clase a usar cuando se especifican las dimensiones
de un cubo
Caja (double longitud) {
ancho = alto = profundidad = longitud;
}

// Calcular y retornar el volumen


double volumen () {
return ancho * alto * profundidad;
}
}
class CajaPrueba {
public static void main (String args [ ] ) {
// Creación de cajas usando los distintos constructores
Caja objCaja1 = new Caja(10, 20, 15);
Caja objCaja2 = new Caja();
Caja objCaja3 = new Caja(7);
double volumen;
// Calcula volumen de la primera caja
volumen = objCaja1.volumen();
System.out.println(“Volumen de la caja #1: ” + volumen);
// Calcula volumen de la segunda caja
volumen = objCaja2.volumen();
System.out.println(“Volumen de la caja #2: ” + volumen);
// Calcula volumen de la tercera caja
volumen = objCaja3.volumen();
System.out.println(“Volumen de la caja #3: ” + volumen);
}
}
Programación II 89

Como se puede ver, el constructor sobrecargado apropiado es llamado,


basado en los argumentos especificados cuando se ejecuta la creación
del objeto correspondiente. Este ejemplo muestra tres posibilidades para
crear objetos tipo Caja, lo cual, como indica Wu (2001), incrementa la
reusabilidad de la clase: si la clase sólo tiene un constructor, sólo hay
una única forma de crear una instancia u objeto; si la clase ofrece varios
constructores, existe la opción de elegir el constructor que mejor se adapte
a los requerimientos de la situación bajo análisis.6.-

6. DESCRIPCIÓN DE HERENCIA
La herencia es el proceso de derivar una nueva clase a partir de otra
clase existente, en opinión de Lewis y Chase (2006), este proceso va a
permitir que la nueva clase contenga, de manera automática, las variables
y métodos de la clase original. En este punto, la clase derivada puede ser
modificada para añadir nuevas variables y métodos. Utilizando la herencia,
se puede crear una clase general que defina los rasgos comunes a un
conjunto de elementos relacionados. Los rasgos de esta clase pueden,
entonces, ser heredados por otras clases más específicas, cada una de las
cuales añade los atributos que las hacen únicas.
En este orden, la clase que se hereda puede ser considerada como una
clase original. La que recibe la herencia es una clase derivada. Por lo tanto,
una clase derivada es una versión especializada de una clase general. La
clase derivada heredará todos los miembros definidos por la clase general
y añadirá sus propios elementos únicos.
En Java, es posible heredar, no solo atributos, sino también métodos
de una clase a otra. Para heredar de una clase, se usa la palabra clave
extends. Si Carro y Vehiculo son dos clases, en donde la primera hereda
los rasgos de la segunda, entonces se aplica, en la definición de la clase
Carro la siguiente cláusula:

class Carro extends Vehiculo;

En el siguiente ejemplo, continuando la situación planteada, se comienza


definiendo la clase Vehiculo (la clase original o clase a ser heredada), con
un atributo y un método:
90 Ángel Pérez

class Vehiculo {
protected String marca = “Ford”; // Atributo de la clase Vehiculo
public void corneta() { // Método de la clase Vehiculo
System.out.println(“Tuut, tuut!”);
}
}

El atributo de la clase Vehiculo tiene el modificador de acceso protected


(mencionado en la sección CONTROL DE ACCESO A LOS MIEMBROS DE
CLASES). Si se configurara como private, la clase Carro no podría acceder
a él. Como segundo paso, se crea la clase Carro, la cual va a heredar los
miembros de la clase Vehiculo:

class Carro extends Vehiculo {


private String modeloNombre = “Mustang”; // Atributo de la clase
Carro
public static void main(String[] args) {

// Crea un objeto llamado miCarro


Carro miCarro = new Carro();

// Llama al método corneta() (de la clase Vehiculo) en el objeto


miCarro
miCarro.corneta();

// Presenta la marca (Atributo de la clase Vehiculo) y el modelo


(Atributo de la clase Carro)
System.out.println(miCarro.marca + “ “ + miCarro.modeloNombre);
}
}
Como indican Lewis y Chase (2006), el proceso de derivación establece
un tipo específico de relación entre dos clases: una relación de tipo es-un.
Programación II 91

Este tipo de relación significa que la clase derivada es una versión más
específica que la original. Si se aplica esto al caso anterior, se tiene que la
clase Carro es-un Vehiculo, pero no se cumple lo inverso (todo Vehiculo
es-un Carro).

7. VENTAJAS DE LA HERENCIA
Al respecto, Deitel y Deitel (2012), acotan que con la herencia se puede
ahorrar tiempo durante el desarrollo de un programa, basando las nuevas
clases en software ya probado y depurado. Esto también aumenta la
probabilidad de que un sistema sea implementado y mantenido eficazmente,
porque al crear una clase, en lugar de declarar miembros completamente
nuevos, se puede designar que la nueva clase debe heredar los miembros
de una clase existente. Luego, la herencia es útil para la reutilización de
código: permite aprovechar código (atributos y métodos) de una clase
existente cuando se crea una nueva clase.
Como se vió en la sección anterior, a través del uso de la palabra clave
“extends”, las nuevas clases pueden heredar los miembros de una clase ya
existente, lo cual incluye las clases pertenecientes a las bibliotecas de Java,
como se muestra en el siguiente ejemplo:

// Demuestra las figuras rellenas.


import java.awt.Color; //Importa la clase Color de la librería java.awt
import java.awt.Graphics; //Importa la clase Graphics de la librería
java.awt
import javax.swing.JPanel; //Importa la clase JPanel de la librería java.
swing

//Se crea una nueva clase, DibujarCaraSonriente, que hereda de la


clase JPanel
public class DibujarCaraSonriente extends JPanel {
public void paintComponent( Graphics g ) {
super.paintComponent( g );
92 Ángel Pérez

// Dibuja la cara
g.setColor( Color.YELLOW );
g.fillOval( 10, 10, 200, 200 );

// Dibuja los ojos


g.setColor( Color.BLACK );
g.fillOval( 55, 65, 30, 30 );
g.fillOval( 135, 65, 30, 30 );

// Dibuja la boca
g.fillOval( 50, 110, 120, 60 );

// Convierte la boca en una sonrisa


g.setColor( Color.YELLOW );
g.fillRect( 50, 110, 120, 30 );
g.fillOval( 50, 120, 120, 40 );
} // Fin del metodo paintComponent
} // Fin de la clase DibujarCaraSonriente

De acuerdo con lo mostrado en el ejemplo, la clase DibujarCaraSonriente


se crea heredando los miembros de la clase JPanel (de la librería java.
swing), lo cual facilita el dibujo (en este caso, de una cara sonriente), el
cual se completa a través del uso de varios métodos (setColor(),fillOval()
y fillRect()), disponibles a través del objeto g (tipo Graphics, de la librería
java.awt). Las organizaciones pueden desarrollar sus propias bibliotecas de
clases y pueden aprovechar otras disponibles. Esto facilitará el desarrollo
de software de manera más rápida.

8. SUPERCLASES Y SUBCLASES
En cuanto a las explicaciones de las dos secciones anteriores, en Java,
el “concepto de herencia” requiere, como mínimo, dos tipos de clases:
Programación II 93

• subclase (clase hija o clase derivada) - la clase que hereda de otra


clase

• superclase (clase padre o clase original) - la clase de la cual se hereda

Aunque sólo se limita a dos tipos de clases, no significa que sólo se


puedan desarrollar dos clases. La siguiente figura muestra, de manera
esquemática, algunas clases de animales: Reptil, Pájaro y Mamífero. A
su vez, debajo de Reptil se encuentran Serpiente y Lagarto. Siguiendo
el esquema, se encuentra que Loro es un tipo de Pájaro, mientras que
Caballo y Murciélago pertenecen a la clase Mamífero:

Figura 2.

Una jerarquía de clases

Fuente: Lewis y Chase (2006)

Usando las clases de la figura anterior, Reptil hereda de la clase Animal,


así como también lo hacen Pájaro y Mamífero. Es posible crear objetos tipo
Animal, Reptil, Pájaro o Murciélago, dada la independencia y autonomía de
clases. Además, una subclase puede ser una superclase para otra subclase;
94 Ángel Pérez

en el caso de Serpiente y Lagarto, ambos son de tipo Reptil. Esto permite


crear una jerarquía de clases, con tantos niveles como sea requerido.
En Java, la jerarquía de clases comienza con la clase Object (ubicada
en el paquete java.lang), que cada clase en Java extiende directa o
indirectamente (o “hereda de”). Según Eck (2019), la forma general
de una declaración de clase que hereda una superclase, se muestra a
continuación:

class nombre-subclase extends nombre-superclase {


// Cuerpo de la clase
}
Como ejemplo, se tiene:
class Reptil extends Animal {
// Cuerpo de la clase
}
class Serpiente extends Reptil {
// Cuerpo de la clase
}
class Lagarto extends Reptil {
// Cuerpo de la clase
}

Sólo se puede especificar una superclase para cualquier subclase que


se cree. Java no soporta la herencia de múltiples superclases en una sola
subclase, como indican Joyanes, y Zahonero, (2008). Para que el concepto
de herencia funcione adecuadamente en una relación de jerarquía, todos
los rasgos (atributos y métodos) de la superclase deben ser accesibles
por todas las subclases (es decir, no se puede definir ningún rasgo como
private en la superclase, ya que podría producir una violación de acceso
que detendría la ejecución del programa).
La palabra reservada “super” según Eck (2019), los constructores no
se heredan. Esto significa que, si se crea una subclase, los constructores
de la superclase no van a formar parte de aquella; hay que definir nuevos
constructores en la subclase, en caso de requerirlo. A primera vista, esto
Programación II 95

podría ser un problema, si hay un constructor en la superclase que sea


requerido por la subclase y una manera de resolverlo sería repetir todo
el trabajo en la subclase. Otra manera, es utilizar super como la primera
instrucción en el constructor de la subclase, como muestra el siguiente
ejemplo, en la cual se asume la existencia de la clase Dado que tiene un
constructor que toma dos números enteros como parámetros:

public class DadoGrafico extends Dado {


public DadoGrafico() { // Constructor de la clase.
super(3,4); // Llama al constructor de la clase Dado, con parámetros
3 y 4.
inicializacionGrafica(); //Ejecuta tareas específicas de la clase
DadoGrafico
}
// Cuerpo de la clase DadoGrafico
}

En el ejemplo anterior, la instrucción “super(3,4);” llama al constructor


de la superclase y pasa los parámetros requeridos por éste. Como se
indicó anteriormente, esta instrucción debe ser la primera en el constructor
de la subclase.
En la palabra reservada “this”, a continuación, se presenta el ejemplo
mostrado en la sección 2.1 (abstracción, encapsulado y polimorfismo):

public class Persona {


private String nombre; // private = el acceso a la variable está
restringido

// Método Get; permite el acceso al valor guardado en la variable


nombre
public String getNombre() {
return nombre; // Devuelve el valor guardado en la variable nombre
}
96 Ángel Pérez

// Método Set; permite fijar el valor a guardar por la variable nombre


public void setNombre(String nuevoNombre) {
this.nombre = nuevoNombre;//El valor de nuevoNombre se asigna
a nombre
}
}

En dicho ejemplo, está la siguiente instrucción de asignación:

this.nombre = nuevoNombre;

En la misma, la variable de instancia nombre va precedida de la


palabra this, la cual permite realizar una referencia al objeto actual, como
indica Cosmina (2018). Por lo tanto, cada vez que se cree un objeto tipo
Persona, this.nombre será el valor del campo nombre del objeto actual. A
continuación, se presenta un ejemplo más amplio para ilustrar el uso de
la palabra this:

public class Humano {


static final int TiempoDeVida = 100;
private String nombre;
private int edad;
private float altura;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public int getEdad() {
return edad;
Programación II 97

}
public void setEdad(int edad) {
this.edad = edad;
}
public float getAltura() {
return altura;
}
}

Como se puede ver en el ejemplo anterior, en los métodos cuyo nombre


comienza con el prefijo set se efectúa la asignación de valores (inicialización)
de las variables de instancia (o atributos) de la clase Humano, usando
para ello parámetros del mismo nombre. Dentro del cuerpo de la clase,
this accede a los campos del objeto actual, cuando hay parámetros en
los métodos que tengan el mismo nombre. En este ejemplo, no hay un
constructor explícitamente definido, por lo que el compilador genera uno
por omisión.
En este orden de ideas, el constructor por omisión, llama a super()
que invoca al constructor sin argumentos de la clase Object, que inicializa
todos los campos con valores por omisión, según indica Cosmina (2018).
La siguiente clase completa el ejemplo, creando un objeto tipo Humano
y asignando valores a los atributos del objeto creado e imprimiendo cada
uno de ellos:
public class PruebaHumano {
public static void main(String[ ] args) {
Humano humano = new Humano();
humano.nombre = “Juan”;
humano.edad = 40;
humano.altura = 1.91f;
System.out.println(“nombre: “ + humano.nombre);
System.out.println(“edad: “ + humano.edad);
System.out.println(“altura: “ + humano.altura);
}
}
ACTIVIDADES DE AUTOEVALUACIÓN

Nombre: _______________________________
UNIVERSIDAD
Privada
DR. RAFAEL BELLOSO CHACÍN Cédula: __________________ Sección: ______

Responda los siguientes enunciados. Valor: 1 pts.

1. Ser una superclase para una subclase significa que la superclase


no puede ser utilizada por sí misma
a. Verdadero
b. Falso

2. Sea la siguiente declaración de clase:


class Obrero extends Trabajador
a. ¿Cuál es la superclase?
b. ¿Cuál es la clase que hereda?

3. Complete el siguiente código, usando la instrucción for-each:


public class ListaCarros {
public static void main(String[] args) {
String [ ] vocales = {“a”, “e”, “i”, “o”, “u”};
for (String I : vocales) {
System.out.println();
}
}
}

4. ¿Qué se debe realizar para convertir un atributo de una clase en


una constante? De un ejemplo concreto en Java.
100 Ángel Pérez

5. En el siguiente código, corrija los errores:


// Ejemplo de paso de objetos a métodos
class Prueba {
float a, b;
Prueba (int I, int j) {
a = I;
b = j;
}
// Retorna verdadero si el objeto o es igual al objeto que invoca
Boolean igualA (Prueba o) {
if(o.a == a && o.b == b) return false;
else return false;
}
}
class PasaObj {
public static void main (String args [ ]) {
Prueba objeto1 = new Prueba(100, 22;
Prueba objeto2 = Prueba(100, 22);
Prueba objeto3 = new Prueba(-1, -1);
System.out.println(“objeto1 == objeto2 ” + objeto1.igualA(obj2));
System.out.println(“objeto1 == objeto3 ” + obj1.igualA(objeto3));
}
}
UNIDAD III
RECURSIVIDAD
102 Ángel Pérez

UNIDAD III
RECURSIVIDAD

1. DEFINICIÓN
Para Aguirre (2020), la recursividad es la característica en la
programación que permite hacer un llamado a un método desde el mismo
método, esta simplifica el desarrollo y equivale a una iteración en una
estructura de repetición como while o for. En este orden, al aplicar esta
definición, el método necesariamente debe retomar un valor, recibir por
parámetro al menos un valor, implementando una condición de ruptura
del proceso recursivo aplicando una función del mismo proceso recursivo.
Ante esto, los programas vistos en las unidades precedentes de este
libro están estructurados en métodos que deben ser invocados (o llamados)
para ser usados, ya sea de manera directa o a través de otro método,
como muestran los ejemplos siguientes:

System.out.println(“Buen dia.”);
System.out.println(txt.toUpperCase());
int num = entrada.nextInt();

En la primera instrucción del ejemplo anterior, se invoca el método


println() y se le pasa como argumento la frase “Buen dia.”; en el segundo
ejemplo, también se invoca al método println(), pero el argumento
que se pasa es la instrucción txt.toUpperCase(), en donde el método
toUpperCase(), convierte a mayúsculas el contenido de la variable txt (que
debe ser tipo String); en este caso, se ejecuta primero la conversión a
mayúsculas y luego la impresión en cónsola del resultado anterior. En el
tercer caso, la variable num almacena un valor de tipo int ingresado por
el usuario, a través del método nextInt(), invocado a través del objeto
entrada (de tipo Scanner).
A diferencia de los ejemplos mostrados anteriormente, un método
capaz de invocarse a sí mismo se conoce como método recursivo, según
lo establecen Deitel y Deitel (2012). En este orden de ideas, un método
recursivo puede llamarse a sí mismo ya sea directa o indirectamente a
través de otro método.
Programación II 103

Como ejemplo de recursividad, se tiene la serie o sucesión de Fibonnacci.


La siguiente situación, según indican Brassard y Bratley (1997), es un
ejemplo de aplicación de dicha serie: se tiene una pareja de conejos,
que dan lugar a dos descendientes por mes. Los descendientes, a su vez,
comienzan a reproducirse dos meses después y así sucesivamente. Luego,
si se compra una pareja de conejos en el mes 1, seguirá habiendo una
pareja en el mes 2. En el mes 3, empezarán a reproducirse y habrá tres
parejas; en el mes 5 habrá cinco parejas y así sucesivamente. Asumiendo
que no muere ningún conejo, el número de parejas que se tendrá cada
mes viene dado por los términos de la sucesión de Fibonacci:

f0 = 0; f1 = 1 y
fn = fn-1 + fn-2 para n>=2

El valor de fn (cantidad de parejas de conejos en el mes n, en este


caso), está dado en función de la cantidad de parejas de los dos meses
anteriores (n-1 y n-2, de manera recursiva) respectivamente. Luego, mes
por mes, durante nueve meses, se obtiene la siguiente sucesión: 0, 1, 1,
2, 3, 5, 8, 13, 21, 34. Cuando n=0 o n=1 es que se conoce el valor de
fn y estos valores son el punto de partida para armar la serie o sucesión.
El algoritmo correspondiente a la situación anterior, según lo establecen
Brassard y Bratley, (1997) se puede formular como sigue:

función Fibonacci(n)
si n < 2 entonces devolver n
sino devolver Fibonacci(n-1) + Fibonacci(n-2)
En este orden, los enfoques de resolución de problemas recursivos
tienen varios elementos en común. Cuando se llama a un método recursivo
para resolver un problema, éste en realidad es capaz de resolver sólo
los casos más simples o los casos base (n < 2, en el caso anterior). Si el
método se llama con un caso base, devuelve un resultado (0 o 1, en el
caso anterior).
Si el método se llama con un problema más complejo, devuelve una
o más llamadas al método (Fibonacci(n-1) + Fibonacci(n-2), según el
valor de n). Debido a que este nuevo problema se parece al problema
original, el método llama a una nueva copia de sí mismo para trabajar en
el problema más pequeño – esto se conoce como una llamada recursiva y
104 Ángel Pérez

también se conoce como el paso de recursión, según lo establecen Deitel


y Deitel (2012). El paso de recursión normalmente incluye una declaración
de retorno, porque su resultado se combinará con la parte del problema
que el método supo resolver para formar un resultado que será devuelto
a la llamada original.
Asi mismo, el paso de recursividad se ejecuta mientras la llamada
al método original todavía está activa (es decir, no ha terminado de
ejecutarse). Puede resultar en muchas más llamadas recursivas a medida
que el método divide cada nuevo subproblema en dos partes conceptuales
(caso base o nueva llamada al método o función).
Para que la recursión termine eventualmente, cada vez que el método
se llama a sí mismo con una versión más simple del problema original,
la secuencia de problemas cada vez más pequeños debe converger en
un caso base. Cuando el método reconoce el caso base (a través de una
decisión si-entonces), devuelve un resultado a la copia anterior del método.
Se produce una secuencia de devoluciones hasta que la llamada al método
original devuelve el resultado final.
Luego, de lo descrito anteriormente se obtiene que un método recursivo
consta de tres partes: un caso base (por lo menos), una instrucción de
decisión (if-else), para decidir si se ha llegado al caso base o no y un paso
de invocación con un nuevo caso a descomponer.

2. MÉTODOS RECURSIVOS
En lo que se refiere a la programación en Java, la recursión es el atributo
que permite que un método se llame a sí mismo, de acuerdo a lo indicado
por Downey y Mayfield (2016). Se dice que un método que se llama a sí
mismo es recursivo. Un ejemplo de recursividad es el cálculo del factorial
de un número entero, que se presenta a continuación:
0! = 1
n! = n (n – 1)!
// Cálculo del factorial en forma recursiva
class Factorial {
//Comienzo del método recursivo
int fact(int n) {
int result;
Programación II 105

if(n==0) return 1; //Determinación del caso base


result = fact(n-1)*n; //Llamada recursiva al método fact()
return result; //Devuelve el resultado definitivo
}
}
class Recursion {
public static void main(String args[]) {
Factorial f = new Factorial(); //Creación del objeto tipo
Factorial
System.out.println(“Factorial de 3 es “ + f.fact(3));//
Llamada a fact
System.out.println(“Factorial de 4 es “ + f.fact(4)); //
Llamada a fact
}
}

En el cálculo de el factorial, sucede algo similar a lo visto en la sucesión


de Fibonacci. Cuando se llama al método fact( ) con un argumento de 0,
la función devuelve 1; de lo contrario, devuelve el producto de fact(n-1)
* n. Para evaluar esta expresión, fact() se llama con argumento n-1.
Este proceso se repite hasta que n es igual a 0 y las llamadas al método
comienzan a retornar. Si se quiere calcular el factorial de 3, la primera
llamada a fact(), con el valor de 3, causará una segunda llamada a ser
hecha con un argumento de 2.
Esta invocación causará que fact() se llamará una tercera vez con un
argumento de 1. Esta llamada devolverá 1, que es entonces multiplicado
por 2 (el valor de n en la segunda invocación). Este resultado (que es 2)
es retornado a la invocación original y multiplicado por 3 (el valor original
de n). Esto da la respuesta, 6. La ejecución del factorial genera una pila
en memoria que contiene las invocaciones realizadas al método fact(), en
donde el fondo corresponde a la llamada inicial (fact(3), en el ejemplo) y el
tope se completa con el caso base (fact(0)). En este punto, se comienzan
a resolver las llamadas, hasta resolver el factorial de 3.
106 Ángel Pérez

Tabla 1

Tabla de las invocaciones al método fact

Llamada Resultado
1*(fact(0)) 1
2*(fact(1)) 1
3*(fact(2)) 2*1
(fact(3)) 3*2

Fuente: Pérez (2020)

3. RECURSIVIDAD VS. ITERACIÓN


El cálculo del factorial, según Deitel y Deitel (2012), puede ser realizado
de la siguiente manera:

n * (n-1) * (n-2) *…* 1

Dicha expresión puede ser implementada, de forma iterativa, en Java


usando una instrucción for, como muestra el fragmento de código siguiente
(la variable numero guarda el valor al cual se le va a calcular el factorial):

factorial = 1;
for ( int contador = numero; contador >= 1; contador-- )
factorial *= contador;

La alternativa a usar un método recursivo es aplicar un método iterativo,


basado en el uso de una instrucción de repetición (for, while o do-while),
por lo que el siguiente resumen compara las dos estrategias.
Programación II 107

Cuadro 7
Comparación entre recursividad e iteración

Recursividad Iteración
Basada en una instrucción de Basada en una instrucción de
control o selección (if, if-else o control o repetición (for, while o
switch) do- while)
El proceso involucra repetición El proceso involucra repetición de
llamadas sucesivas al método manera explícita
El proceso termina cuando se El proceso termina cuando se
alcanza el caso base. alcanza la condición final.
El proceso se mantiene produciendo El proceso se mantiene modificando
versiones más pequeñas del un contador, hasta que éste
problema original, hasta que un alcanza un valor que hace que la
caso base es alcanzado. continuación del lazo falla.
Se puede generar un lazo infinito Se puede generar un lazo infinito si
si el proceso no logra alcanzar un la condición final no se alcanza en
caso base. ningún momento.

Fuente: Pérez (2020)

4. RESOLUCIÓN DE PROBLEMAS CON RECURSIÓN


Si se va a usar la recursividad como enfoque de solución a un problema,
se debe tener en cuenta lo siguiente: La recursión tiene muchos aspectos
negativos. Invoca repetidamente el mecanismo y, en consecuencia,
incrementa la sobrecarga de llamadas a los métodos, como indican Deitel
y Deitel (2012).
Esta repetición puede ser costosa en términos de tiempo de procesador
y espacio de memoria. Cada llamada recursiva hace que se cree otra
copia del método; este conjunto de copias puede consumir un espacio de
memoria considerable. Por el contrario, la iteración se produce dentro de
un método, por lo que se evitan las llamadas repetidas a los métodos y la
asignación de memoria adicional.
Como se observa en el caso del factorial, un problema que puede
ser resuelto iterativamente también puede ser resuelto recursivamente
(no se cumple a la inversa). En criterio de Downey y Mayfield (2016),
108 Ángel Pérez

normalmente, se prefiere un enfoque recursivo sobre un enfoque iterativo,


cuando el enfoque recursivo refleja el problema de manera más natural y da
como resultado un programa que es más fácil de entender y depurar, como
se puede apreciar en los ejemplos presentados en las secciones anteriores,
además, un enfoque recursivo a menudo puede ser implementado con
menos líneas de código.
En este orden, una situación en la cual se requiere el uso de métodos
recursivos es en el recorrido de árboles binarios , ya que los algoritmos a
utilizar son muy simples, pero son de naturaleza recursiva, como lo señalan
Cairó y Guardati (2006):

4.1. Recorrido en preorden

• Visitar la raíz
• Recorrer el subárbol izquierdo en preorden
• Recorrer el subárbol derecho en preorden

4.2. Recorrido en inorden

• Recorrer el subárbol izquierdo en inorden


• Visitar la raíz
• Recorrer el subárbol derecho en inorden

4.3. Recorrido en postorden

• Recorrer el subárbol izquierdo en postorden


• Recorrer el subárbol derecho en postorden
• Visitar la raíz

Como se observa de los algoritmos mostrados anteriormente, sólo


se requieren tres pasos para efectuar el recorrido; la diferencia estriba
en el orden de visita de cada elemento del árbol. En tal sentido, una
Programación II 109

implementación en Java de los algoritmos anteriores es realizada por


Joyanes y Zahonero (2008), la cual se presenta a continuación:

//Recorrido de un árbol binario en preorden


public static void preorden(Nodo r)
{
if (r != null)
{
r.visitar();
preorden (r.subarbolIzdo());
preorden (r.subarbolDcho());
}
}
// Recorrido de un árbol binario en inorden
public static void inorden(Nodo r)
{
if (r != null)
{
inorden (r.subarbolIzdo());
r.visitar();
inorden (r.subarbolDcho());
}
}
// Recorrido de un árbol binario en postorden
public static void postorden(Nodo r)
{
if (r != null)
{
postorden (r.subarbolIzdo());
postorden (r.subarbolDcho());
110 Ángel Pérez

r.visitar();
}
}
void visitar( ) {
System.out.print(dato + “ “);
}

Como se ve en el ejemplo, la implementación es directa; cada método


contiene los tres pasos vistos en cada algoritmo, así como agregar el
parámetro r (objeto tipo Nodo ) y la instrucción de decisión, para verificar
que el nodo visitado no este vacío.

5. MÉTODOS DE ORDENACIÓN RECURSIVOS


Para Lewis y Chase (2006), la ordenación es el proceso de disponer de un
grupo de elementos en un orden determinado, ascendente o descendente,
basándose en algún tipo de criterio. Los elementos numéricos se pueden
ordenar de manera creciente o decreciente, de acuerdo al valor del
elemento. Como ejemplo de lo anterior, se puede ordenar alfabéticamente
una lista de nombres o colocar una lista de resultados en orden numérico
descendente. En este orden, Joyanes y Zahonero (2008), establecen en
terminología de ordenación, el elemento por el cual está ordenado un
conjunto de datos se denomina clave.
Por su parte, Cairó y Guardati (2006), indican que los métodos de
ordenación se clasifican en dos grandes categorías: ordenación de arreglos
(ordenación interna; los elementos se encuentran en la memoria principal
de la computadora) y ordenación de archivos (ordenación externa; los
elementos se encuentran almacenados en dispositivos de almacenamiento
secundario, como discos).
Se debe tomar en cuenta que los métodos a revisar se aplican sobre
conjuntos de datos almacenados en arreglos (vectores o matrices) y que
el resultado final (el arreglo ordenado) será el mismo, sin importar el
algoritmo que se utilice para ordenar el conjunto de datos. La elección del
algoritmo afecta sólo al tiempo de ejecución y al uso de la memoria del
programa.
En esta sección se presentan dos algoritmos de clasificación de tipo
Programación II 111

recursivo. El primer algoritmo (ordenación por mezcla, merge sort) ordena


una lista dividiéndola recursivamente en dos mitades, hasta que cada
sublista tiene un único elemento, y luego mezclando estas sublistas en
orden, según lo establecen Lewis y Chase (2006). El algoritmo de ordenación
rápida particiona la lista de elementos y luego ordena recursivamente las
dos particiones.

5.1. Ordenación por mezcla


Según indican Deitel y Deitel (2012), el algoritmo de ordenación por
mezcla toma un arreglo dividiéndolo en dos subarreglos de igual tamaño,
ordena cada subarreglo, y luego los fusiona en un solo arreglo. Con un
número impar de elementos, el algoritmo crea los dos subarreglos, de tal
manera que uno tiene un elemento más que el otro. La implementación de
este algoritmo es recursiva.
En este orden, el caso base es un arreglo con un elemento que,
por supuesto, está ordenado, por lo que el orden de fusión retorna
inmediatamente en este caso. El paso de recursión divide el arreglo en
dos piezas aproximadamente iguales, las clasifica recursivamente, y luego
fusiona los dos arreglos clasificados en uno más grande, clasificado.
A continuación, se presenta una parte de la implementación en Java,
que muestra los métodos: ordenar(), ordenarArreglo() y combinar(). De
los tres métodos anteriores, ordenarArreglo(), es de tipo recursivo, en
donde cada llamada involucra una mitad del arreglo a ordenar.

// Llama al método de división recursiva para comenzar el ordenamiento


por combinación
public void ordenar()
{
ordenarArreglo( 0, datos.length - 1 ); // divide todo el arreglo
} // Fin del método ordenar

// Divide el arreglo, ordena los subarreglos y los combina en un


arreglo ordenado
private void ordenarArreglo( int inferior, int superior )
{
112 Ángel Pérez

// Evalúa el caso base; el tamaño del arreglo es igual a 1


if ( ( superior - inferior ) >= 1 ) // si no es el caso base
{
int medio1 = ( inferior + superior ) / 2; // calcula el elemento
medio del arreglo
int medio2 = medio1 + 1; // calcula el siguiente elemento arriba

// Divide el arreglo a la mitad; ordena cada mitad (llamadas


recursivas)
ordenarArreglo( inferior, medio1 ); // primera mitad del arreglo
ordenarArreglo( medio2, superior ); // segunda mitad del arreglo

// Combina dos arreglos ordenados después de que regresan las


llamadas de división
combinar ( inferior, medio1, medio2, superior );
} // Fin de if
} // Fin del método ordenarArreglo

// Combina dos subarreglos ordenados en un subarreglo ordenado


private void combinar( int izquierdo, int medio1, int medio2, int
derecho )
{
int indiceIzq = izquierdo; // índice en subarreglo izquierdo
int indiceDer = medio2; // índice en subarreglo derecho
int indiceCombinado = izquierdo; // índice en arreglo de trabajo
temporal
int [ ] combinado = new int[ datos.length ]; // arreglo de trabajo

// Combina los arreglos hasta llegar al final de uno de ellos


while ( indiceIzq <= medio1 && indiceDer <= derecho )
{
Programación II 113

// Coloca el menor de dos elementos actuales en el resultado


// y lo mueve al siguiente espacio en los arreglos
if ( datos[ indiceIzq ] <= datos[ indiceDer ] )
combinado[ indiceCombinado++ ] = datos[ indiceIzq++ ];
else
combinado[ indiceCombinado++ ] = datos[ indiceDer++ ];
} // Fin de while

// Si el arreglo izquierdo está vacío


if ( indiceIzq == medio2 )
// Copia el resto del arreglo derecho
while ( indiceDer <= derecho )
combinado[ indiceCombinado++ ] = datos[ indiceDer++ ];
else // el arreglo derecho está vacío
// Copia el resto del arreglo izquierdo
while ( indiceIzq <= medio1 )
combinado[ indiceCombinado++ ] = datos[ indiceIzq++ ];

// Copia los valores de vuelta al arreglo original


for ( int i = izquierdo; i <= derecho; i++ )
datos[ i ] = combinado[ i ];

} // Fin del método combinar

5.2. Ordenación rápida (Quicksort)


Este algoritmo recibe su nombre de su autor, Tony Hoare, de acuerdo
a lo señalado por Joyanes y Zahonero (2008). El algoritmo se basa en
dividir los n elementos de la lista a ordenar en dos partes separadas por un
elemento: una partición izquierda, un elemento central denominado pivote
y una partición derecha. Idealmente, el pivote se debe elegir de modo que
se divida la lista por la mitad.
114 Ángel Pérez

Una vez que el pivote ha sido elegido, se utiliza para ordenar el resto de
la lista en dos sublistas: una tiene todas las claves menores que el pivote
y la otra el resto. Estas dos listas parciales se ordenan recursivamente. La
lista final ordenada se consigue concatenando la primera sublista, el pivote
y la segunda sublista, en ese orden, en una única lista. La primera etapa
del algoritmo es la división o particionamiento recursivo de la lista, hasta
que todas las sublistas constan de un elemento.
La implementación del algoritmo se realiza de manera recursiva; el
método quicksort() con el parámetro a[ ], llama al método privado del
mismo nombre, con los argumentos a[ ], los índices inferior y superior del
arreglo, como se muestra a continuación.

public static void quicksort(double a[ ]) {


quicksort(a, 0, a.length-1);
}

private static void quicksort(double a[], int primero, int ultimo) {


int i, j, central;
double pivote;
central = (primero + ultimo)/2; //Cálculo de la posición central de
la lista
pivote = a[central]; //Se crea el pivote con el valor hallado en la
posición central
i = primero;
j = ultimo;
do {
while (a[i] < pivote) i++;
while (a[j] > pivote) j--;

if (i <= j)
{
intercambiar(a, i, j);
Programación II 115

i++;
j--;
}
}while (i <= j);

if (primero < j)
quicksort(a, primero, j); // mismo proceso con sublista izquierda

if (i < ultimo)
quicksort(a, i, ultimo); // mismo proceso con sublista derecha
}

private static void intercambiar (double v, int i, int j)


{
double aux = v[i];
v[i] = v[j];
v[j] = aux;
}

El código anterior incluye el método intercambiar, que es invocado por


el método privado quicksort( ), cada vez que se encuentra un elemento
menor (o mayor) que el pivote.
Como se indicó al principio de esta sección, el resultado final (el arreglo
ordenado) será el mismo, sin importar el algoritmo que se utilice para
ordenar el conjunto de datos. La elección del algoritmo depende del tiempo
de ejecución y del uso de la memoria del programa. Para Cairó y Guardati
(2006), el método de ordenación rápida es el algoritmo de ordenación
interna más rápido que existe en la actualidad, el cual se basa o tiene su
origen en el método de intercambio directo, el peor de todos los métodos
directos.
ACTIVIDADES DE AUTOEVALUACIÓN

Nombre: _______________________________
UNIVERSIDAD
Privada
DR. RAFAEL BELLOSO CHACÍN Cédula: __________________ Sección: ______

Responda los siguientes enunciados. Valor: 1 pts.

1. Un método es recursivo si
a. Presenta un caso base y una instrucción if para determinar si se ha
llegado al mismo o no
b. Presenta una llamada a sí mismo
c. Presenta un caso base y una llamada a sí mismo
d. Todas las anteriores
e. Ninguna de las anteriores

2. En el siguiente ejemplo, modifique el programa para que solicite el


valor a calcular el factorial:
// Cálculo del factorial en forma recursiva
class Factorial {
//Comienzo del método recursivo
int fact(int n) {
int result;
if(n==1) return 1;
result = fact(n-1)*n;
return result;
}
}
class Recursion {
public static void main(String args[]) {
Factorial f = new Factorial();
System.out.println(“Factorial de 3 es “ + f.fact(3));
System.out.println(“Factorial de 4 es “ + f.fact(4));
}
}

3. En la clase Prueba Ordenamiento Seleccion, efectúe los cambios


necesarios, para que el programa solicite al usuario la cantidad de datos a
utilizar.
UNIDAD IV
FLUJOS Y ARCHIVOS
120 Ángel Pérez

UNIDAD IV
FLUJOS Y ARCHIVOS

1. FLUJOS
En las anteriores unidades de este libro, los ejemplos de código
presentados han manejado los datos a través de variables de tipo
primitivo (int, float, por ejemplo), cadenas (tipo String) o a través de
conjuntos homogéneos de elementos (arreglos). Todos ellos tienen como
característica que se almacenan en la memoria principal del computador,
durante la ejecución del programa, lo cual significa que, al terminar la
ejecución, dichos datos se pierden.
Ante esta situación, Joyanes, y Zahonero, (2008), indican que los
archivos tienen como finalidad guardar datos de forma permanente; una
vez que termina la aplicación o programa, los datos siguen disponibles para
que otra aplicación pueda recuperarlos para su consulta o modificación. El
proceso de archivos en Java se hace mediante flujos (streams) o canales,
los cuales conducen los datos entre el programa y los dispositivos externos
(disco duro, disco compacto, memoria portátil o pendrive, entre otros) y
viceversa.
De lo indicado anteriormente, se pueden identificar, como operaciones
básicas, la escritura de datos (para realizar el almacenamiento de datos) y
la lectura (para realizar consultas). Estas operaciones son conocidas como
Entrada/Salida (Input/Output) y se abrevian como E/S (I/O).

1.1. Definición
Para Joyanes y Zahonero (2008), un flujo (stream) es una abstracción
que se refiere a un flujo o corriente de datos que circulan entre un origen
o fuente (productor) y un destino o sumidero (consumidor). Entre el origen
y el destino debe existir una conexión o canal (pipe) por la que corran
los datos. La apertura de un archivo supone establecer la conexión del
programa con el dispositivo que contiene al archivo; por el canal que
comunica al archivo con el programa van a fluir las secuencias de datos.
Abrir un archivo, supone crear un objeto que queda asociado a un flujo. Al
comenzar la ejecución de un programa en Java, se crean automáticamente
tres objetos flujo, por los que pueden circular datos, de entrada, o de
salida. Estos son objetos definidos en la clase System:
datos por pantalla.

System.err: Objeto para salida estándar de errores; permite al program


Programación II 121
salida de errores por pantalla.
System.in: Objeto entrada estándar; permite la entrada al programa de
flujos de bytes
Según desdeyel Deitel
Deitel teclado.(2012), Java ve cada archivo como un f
System.out: Objeto salida estándar; permite al programa la salida de
secuencial de bytes. Cada sistema operativo proporciona un mecanis
datos por pantalla.
para determinar
System.err: el final
Objeto de unestándar
para salida archivo,
de como
errores;un marcador
permite de fin de arc
al programa
la salida de errores por pantalla.
o un conteo del total de bytes en el archivo, que está registrado en
Según Deitel y Deitel (2012), Java ve cada archivo como un flujo
estructura
secuencial dedebytes.
datos administrativos
Cada mantenida
sistema operativo porunelmecanismo
proporciona sistema. Del mis
para determinar el final de un archivo, como un marcador de fin de archivo
modo, un flujo de salida puede referirse a la consola, un archivo de d
o un conteo del total de bytes en el archivo, que está registrado en una
oestructura
una conexión de red. Los mantenida
de datos administrativos flujos son por una formaDellimpia
el sistema. mismo de man
modo, un flujo de salida puede referirse a la consola, un archivo de disco o
entradas
una conexión / de
salidas
red. Lossin
flujosque cada
son una parte
forma limpiade su código
de manejar comprenda
entradas
/ salidas sin que cada parte de su código comprenda la diferencia entre
diferencia entre un teclado y una red, por ejemplo. Java implementa flu
un teclado y una red, por ejemplo. Java implementa flujos dentro de las
jerarquías
dentro dedelas
clases definidasde
jerarquías enclases
el paquete java.io. en el paquete java.io.
definidas

0 1 2 ... n-1
... Fin de archivo

Figura
Figura 3.3.Vista
Vista dearchivo
de un un archivo
de n bytesde n bytes en Java
en Java
Fuente: Deitel, P. y Deitel, H. (2012)
Fuente: Deitel y Deitel (2012)

Un programa Java que procesa una secuencia de bytes (un flu


Un programa Java que procesa una secuencia de bytes (un flujo),
simplemente recibe una indicación del sistema operativo cuando lleg
simplemente recibe una indicación del sistema operativo cuando llega al
final de
final delalasecuencia; el programa
secuencia; no necesita
el programa saber cómo
no necesita la plataforma
saber cómo la platafo
subyacente representa archivos o secuencias. En algunos casos, la
subyacente representa
indicación de fin de archivo archivos
se produce ocomo
secuencias. En En
una excepción. algunos
otros casos
casos, la indicación es un valor de retorno de un método invocado en un
indicación de fin de archivo
objeto de procesamiento de flujo. se produce como una excepción. En o

1.2. Flujos. Jerarquía de clases


Cuando se trata de operaciones de entrada/salida, Eck (2019) indica
que hay que tener en cuenta que hay dos grandes categorías de datos:
datos con formato de máquina y texto legible por el hombre. Los datos con
formato de máquina se representan en forma binaria, de la misma manera
122 Ángel Pérez

que los datos son representados dentro de la computadora, es decir, como


secuencias de ceros y unos. Los datos legibles por los humanos están en
forma de caracteres.
Para tratar las dos grandes categorías de representación de datos,
Java tiene dos grandes categorías de flujos: flujos de bytes para datos
con formato de máquina y flujos de caracteres para datos legibles por
humanos. El hecho de que Java defina dos tipos diferentes de flujos hace
que se necesiten dos conjuntos separados de jerarquías de clase (una para
bytes y otra para caracteres).
Las clases de flujos de E/S estándar que se tratan en esta unidad están
definidas en el paquete java.io, junto con varias clases de apoyo. Se deben
importar las clases de este paquete, para usarlas en el programa. Eso
significa importar clases individuales o poner la directiva “import java.io.*;”
al principio del archivo fuente. Los flujos de E/S se usan para trabajar con
archivos y para hacer comunicaciones a través de una red. A continuación,
se presenta el conjunto de clases de flujo de bytes:

Cuadro 8
Flujos de Bytes

Clases de flujo de bytes Significado


BufferedInputStream Contiene métodos para leer bytes del búfer
(área de memoria).
BufferedOutputStream Contiene métodos para escribir bytes en el
búfer.
ByteArrayInputStream Contiene métodos para leer bytes de un array
de bytes
ByteArrayOutputStream Contiene métodos para escribir bytes en un
array de bytes.
DataInputStream Contiene métodos para leer tipos de datos
primitivos de Java.
DataOutputStream Contiene métodos para escribir tipos de datos
primitivos de Java.
FileInputStream Contiene métodos para leer bytes de un
archivo.
FileOutputStream Contiene métodos para escribir bytes en un
archivo.
Programación II 123

FilterInputStream Contiene métodos para leer bytes de otros


flujos de entrada que utiliza como fuente
básica de datos.
FilterOutputStream Contiene métodos para escribir en otros flujos
de salida.
InputStream Clase abstracta que describe la entrada del
flujo.
Output Stream Clase abstracta que describe la salida del flujo.
ObjectInputStream Contiene métodos para leer objetos.
ObjectOutputStream Contiene métodos para escribir objetos.
PipedInputStream Un flujo de entrada canalizado debe conectarse
a un flujo de salida canalizado.
PipedOutputStream Contiene métodos para escribir en un flujo de
salida canalizado.
PrintStream Flujo de salida que contiene print() y println()
PushbackInputStream Flujo de entrada que permite que los bytes se
devuelvan al stream.
SequenceInputStream Contiene métodos para concatenar múltiples
flujos de entrada y luego leer del flujo
combinado.
Fuente: Schildt (2017)

Un objeto que produce datos a un flujo de bytes pertenece a una de


las subclases de la clase abstracta OutputStream. Los objetos que leen
datos de un flujo de bytes pertenecen a subclases de la clase abstracta
InputStream. Los números escritos en un flujo de tipo OutputStream,
no pueden ser leídos directamente; dichos datos pueden ser leídos en la
computadora con un flujo de tipo InputStream. La escritura y lectura de
los datos será muy eficiente, ya que no hay traducción implicada: los bits
que se usan para representar los datos dentro de la computadora son
simplemente copiados hacia y desde los flujos.
Los flujos de bytes pueden ser útiles para la comunicación directa
entre máquinas, y pueden a veces ser útiles para almacenar datos en
archivos, especialmente cuando se necesitan grandes cantidades de datos
almacenados de manera eficiente, como en grandes bases de datos. A
continuación, se presenta el conjunto de clases de flujo de caracteres:
124 Ángel Pérez

Cuadro 9

Flujos de caracteres

Clases de flujo de caracteres Significado


Contiene métodos para leer caracteres
BufferedReader
en un búfer.
Contiene métodos para escribir
BufferedWriter
caracteres en un búfer.
Contiene métodos para leer caracteres
CharArrayReader
de una matriz de caracteres.
Contiene métodos para escribir caracteres
CharArrayWriter
en una matriz de caracteres.
Contiene métodos para leer desde un
FileReader
archivo.
Contiene métodos para escribir en un
FileWriter
archivo.
Contiene métodos para leer caracteres
FilterReader
en el flujo de entrada subyacente.
Contiene métodos para escribir caracteres
FilterWriter
en el flujo de salida subyacente
Contiene métodos para convertir bytes
InputStreamReader
en caracteres.
LineNumberReader Flujo de entrada que cuenta las líneas.
Contiene métodos para convertir de
OutputStreamWriter
caracteres a bytes.
PipedReader Pipe de entrada.
PipedWriter Pipe de salida.
Flujo de entrada que permite que los
PushbackReader caracteres se devuelvan al flujo de
entrada.
Clase abstracta que describe la entrada
Reader
de flujo de caracteres.
Clase abstracta que describe la salida de
Writer
flujo de caracteres.
Contiene métodos para escribir en la
PrintWriter
consola de salida
Programación II 125

Contiene métodos para leer en una


StringReader
cadena.
Contiene métodos para escribir en una
StringWriter
cadena.
Fuente: Schildt (2017)

Para leer y escribir datos en caracteres legibles, las clases principales


son las clases abstractas Reader y Writer. Todas las clases de flujos de
caracteres son subclases de una de ellas. Si se va a escribir un número
en un flujo de tipo Writer, la computadora debe traducirlo a una secuencia
de caracteres legibles para el ser humano. La lectura de un número en un
flujo de lectura a una variable numérica también implica una traducción, de
una secuencia de caracteres a la cadena de bits apropiada. Los caracteres
se almacenan en la computadora como valores Unicode de 16 bits.
Así, las clases Reader y Writer proporcionan los métodos de lectura y
escritura de bajo nivel. Al igual que en las clases de flujos de bytes, las
operaciones de E/S leen y escriben caracteres en lugar de bytes. El valor
de retorno de read() es -1 si se ha alcanzado el final del flujo de entrada.
En caso contrario, el valor de retorno debe ser transferido al tipo char para
obtener el carácter que se ha leído.
De los flujos mencionados anteriormente, se presentan, a manera
de explicación, BufferedReader y la clase Scanner, para operaciones de
entrada y PrintWriter, para operaciones de salida.

1.2.1. Lectura de la consola de entrada


Actualmente, en aplicaciones comerciales, el método preferido para
leer entrada de la consola es usar un flujo orientado a caracteres, según
lo indica Schildt (2017), esto tiene como ventaja el mantenimiento y la
internacionalización del programa (de ser necesario). La entrada de la
consola se realiza leyendo del flujo System.in. Para obtener un flujo de
caracteres conectado a la consola, se enlaza System.in con un objeto tipo
BufferedReader, por lo que se puede usar como constructor:

BufferedReader(Reader lectorEntrada)

En el caso anterior, lectorEntrada es el flujo que está enlazado a la


instancia de BufferedReader que está siendo creada. Reader es una clase
126 Ángel Pérez

abstracta y una de sus subclases es InputStreamReader, que convierte


datos tipo bytes a caracteres. Para enlazar un objeto InputStreamReader
al flujo System.in, se usa el siguiente constructor:

InputStreamReader(InputStream flujoEntrada);

Reuniendo las piezas anteriores, la siguiente línea de código crea un


objeto tipo BufferedReader que está conectado al teclado:
BufferedReader entrada = new BufferedReader(new
InputStreamReader(System.in));

Después que esta instrucción se ejecuta, la variable entrada, que es un


flujo orientado a caracteres, se conecta a la consola (teclado) a través del
flujo System.in.

1.2.2. Lectura de caracteres


Como lo indica Schildt (2017), para leer un caracter de un objeto tipo
BufferedReader, se usa el método read(). La versión a usar de este método
es la que se presenta a continuación:

int read( ) throws IOException

Cada vez que el método read() es invocado, lee un caracter del flujo
de entrada y lo retorna como un valor de tipo entero. Este método retorna
–1 cuando se intenta leer más allá del final del flujo. Como se muestra en
la definición del método read(), éste maneja (lanza) excepciones del tipo
IOException.
El siguiente ejemplo de código muestra el uso del método read()
leyendo caracteres suministrados por el usuario, hasta que se ingresa el
caracter “q.” En el ejemplo, se observa que cualquier excepción de E/S que
pueda generarse es simplemente lanzada desde main().
import java.io;
public class LeeCaracter {
public static void main(String[] args) throws IOException {
Programación II 127

char c;
BufferedReader entrada = new BufferedReader (new
InputStreamReader (System.in));
System.out.println(“Ingrese una secuencia de caracteres y ‘q’ para
finalizar”);
//Lectura de caracteres ingresados
do {
c = (char) entrada.read();
System.out.println(c);
} while(c!= ‘q’);
}
}

Un ejemplo de corrida del programa se muestra a continuación:

Ingrese una secuencia de caracteres y ‘q’ para finalizar


123abcq
1
2
3
a
b
c
q

En la corrida se muestra el ingreso de un conjunto de caracteres, que


fueron leídos y presentados uno por uno, pero si se requiere el ingreso
de datos más grandes (cadenas de caracteres), se requiere otro tipo de
método para llevar a cabo la tarea.
128 Ángel Pérez

1.2.3. Lectura de cadenas


A diferencia del caso anterior, mencionado por Schildt (2017), para
leer una cadena del teclado, se debe usar el método readLine(). Su forma
general se muestra a continuación:

String readLine( ) throws IOException

En la definición, se observa que el método devuelve un objeto de tipo


String. El siguiente ejemplo de código muestra el uso del método readLine()
leyendo cadenas (líneas enteras de texto, en este caso) suministradas
por el usuario, hasta que se ingresa la palabra “termina”. En el ejemplo,
se observa que cualquier excepción de E/S que pueda generarse es
simplemente lanzada desde main().

import java.io;
public class LecturaLinea {
public static void main(String[] args) throws IOException {
String cadena;
BufferedReader entrada = new BufferedReader (new
InputStreamReader (System.in));
System.out.println(“Ingrese líneas de texto”);
System.out.println(“Ingrese ‘termina’ para finalizar”);
//Lectura de caracteres ingresados
do {
cadena = entrada.readLine();
System.out.println(cadena);
} while(!cadena.equals(“termina”));
}
}
Programación II 129

1.2.4. La clase PrintWriter


Aunque usar el flujo System.out para escribir a la consola es aceptable,
se recomienda su aplicación para propósitos de depuración o en programas
de demostración, como señala Schildt (2017). Para programas del mundo
real, el método recomendado de escritura a la consola, cuando se usa Java,
es a través del flujo PrintWriter, la cual es una clase basada en caracteres
(ver cuadro 9). Esta clase define varios constructores, uno de los cuales es
el que se muestra a continuación:

PrintWriter(OutputStream flujoSalida, boolean descargaActiva);

En la instrucción anterior, flujoSalida es un objeto de tipo OutputStream,


y el parámetro descargaActiva controla si Java descarga (flushes) el flujo
de salida, cada vez que el método println( ) es invocado. Si el parámetro
descargaActiva es verdadero (true), la descarga, automáticamente, toma
lugar. Si no es verdadero, la descarga debe realizarse de manera manual.
La clase PrintWriter soporta los métodos print( ) y println( ) , los
cuales se usan de la misma manera que en el flujo System.out. Si un
argumento no corresponde a uno de los tipos simples, los métodos de la
clase PrintWriter llamarán al método toString( ) y luego presentarán el
resultado. Para escribir en consola, se debe especificar System.out como
el flujo de salida y la descarga automática. Por ejemplo, la siguiente línea
de código crea un objeto (salida) de tipo PrintWriter que se conecta a la
consola de salida:

PrintWriter salida = new PrintWriter (System.out, true);

El siguiente ejemplo de código muestra el uso de la clase PrintWriter


para manejar la consola de salida:

import java.io;
public class EjemploPrintWriter {
public static void main(String[] args) throws IOException {
PrintWriter salida = new PrintWriter (System.out, true);
salida.println(“Esta es una cadena”);
130 Ángel Pérez

int i = -7;
salida.println(i);
double d = 4.5e-7;
salida.println(d);
}
}

La salida de este programa se muestra a continuación:

Esta es una cadena


-7
4.5E-7

1.2.5. La clase Scanner


Esta clase se introdujo para facilitar la lectura de los tipos de datos
básicos de una fuente de entrada de caracteres, según lo indica Eck,
(2019). La clase Scanner está en el paquete java.util. Las rutinas de
entrada se definen como métodos de instancia en la clase Scanner, por
lo que, para usar esta clase, es necesario crear un objeto Scanner. El
constructor especifica la fuente de los caracteres que el Scanner leerá.
El objeto Scanner actúa como un envoltorio para la fuente de entrada.
La fuente puede ser un objeto Reader, InputStream, String, o un Archivo,
entre otras posibilidades. Si se utiliza una cadena como fuente de entrada,
el objeto Scanner leerá los caracteres de la cadena de principio a fin, de
la misma manera que procesaría la misma secuencia de caracteres de
un flujo. Por ejemplo, se puede utilizar un objeto Scanner para leer de la
entrada estándar definiendo:
En la instrucción anterior, siguiendo lo indicado anteriormente, se ha
definido entrada como un objeto de tipo Scanner, que ha sido asociado con
el teclado (entrada estándar, en este caso), especificado en el constructor
de la clase (Scanner).
La clase Scanner tiene métodos de instancia para la lectura de varios
tipos de datos, como se muestra a continuación:
Programación II 131

• next() - lee el conjunto de caracteres de la fuente de entrada y lo


devuelve como una cadena.

• nextInt(), scanner.nextDouble(), etc.- lee el conjunto de caracteres de


la fuente de entrada y trata de convertirlo en un valor de tipo int, double,
etc.

• nextLine() - lee una línea entera desde la fuente de entrada, hasta el


siguiente final de línea, y devuelve la línea como un valor de tipo String. Se
lee y se devuelve una línea entera, incluyendo los caracteres de espacio en
blanco de la línea. El valor de retorno puede ser una cadena vacía.

Ejemplos de uso (aplicados al objeto entrada definido anteriormente):


String nombreUsuario = entrada.nextLine(); // Entrada tipo String
int edad = entrada.nextInt();// Entrada numérica tipo entero (int)
double sueldo = entrada.nextDouble();// Entrada numérica tipo real
(double)

En los ejemplos anteriores, se invoca el método de lectura a través


del objeto creado (en este caso, entrada) y se almacena en una variable,
cuyo tipo de dato debe coincidir con el del método de lectura utilizado.
Todos estos métodos pueden generar excepciones. Si se intenta
leer más allá del final de la entrada, se lanza una excepción del tipo
NoSuchElementException, según lo indica Eck, (2019). Métodos como
nextInt() arrojarán una excepción del tipo InputMismatchException si el
dato de entrada no representa un valor del tipo solicitado.
La clase Scanner tiene muy buenas capacidades de previsión. Se puede
consultar a un objeto tipo Scanner para determinar si hay más datos
disponibles y si el siguiente dato es de un tipo determinado, para lo cual se
tienen los siguientes métodos disponibles:

• scanner.hasNext() - devuelve un valor booleano verdadero, si hay


al menos un dato más en la fuente de entrada.
• scanner.hasNextInt(), scanner.hasNextDouble(), y así
sucesivamente, devuelve un valor booleano verdadero, si hay al menos un
dato más en la fuente de entrada y ese dato representa un valor del tipo
solicitado.
132 Ángel Pérez

• scanner.hasNextLine() - devuelve un valor booleano verdadero. si


hay al menos una línea más en la fuente de entrada.

Ejemplos de uso (aplicados al objeto entrada definido anteriormente):

// Imprime verdadero (true) si hay una línea adicional en la entrada


estándar (teclado):
System.out.println(entrada.hasNextLine());
//Mientras hayan valores de tipo double, el método hasNextDouble()
devuelve true
while ( data.hasNextDouble() ) {

Los métodos mencionados anteriormente sólo devuelven valores


booleanos (verdadero o falso). Esto puede ser usado en instrucciones
repetitivas (while, do-while), para determinar si hay más valores de un tipo
determinado en la entrada de datos; cuando el valor booleano sea falso
(false), termina el proceso de lectura de datos de la entrada.

1.3. Clases Fileinputstream y Fileoutputstream


Schildt (2017), establece que la clase FileInputStream se utiliza para
la lectura de datos orientados a bytes de un archivo de entrada, para lo
cual se crea un objeto tipo InputStream, que efectúa la lectura de bytes
desde el archivo en cuestión. Para esta operación, se puede usar una de
las siguientes definiciones del constructor de la clase:

FileInputStream(String ruta)
FileInputStream(File archivo)

En la primera definición, ruta especifica la dirección completa al archivo


de entrada, mientras que en la segunda el término archivo se refiere a un
objeto tipo File que describe al archivo de entrada. Si el archivo de entrada
no existe, cualquiera de las dos instrucciones puede lanzar una excepción
del tipo a FileNotFoundException. El siguiente ejemplo crea dos objetos
tipo FileInputStream (llamado entrada en ambos casos), que se refieren
al mismo archivo de entrada y aplican las definiciones para el constructor
de la clase:
Programación II 133

FileInputStream entrada = new FileInputStream(“prueba.txt”);


File f = new File(“prueba.txt”);
FileInputStream entrada = new FileInputStream(f);

Es de hacer notar que, aunque el primer constructor sea más


comúnmente usado, el segundo permite examinar el archivo de entrada,
usando los métodos de la clase File (ver Cuadro 10), antes de conectarlo
a un flujo de entrada. Cuando un objeto tipo FileInputStream es creado,
queda disponible para la lectura correspondiente, a través del método
read():

System.out.print((char) entrada.read());

En la instrucción anterior, se efectúa la lectura de un byte proveniente


del archivo de entrada (“prueba.txt”), que es convertido a caracter y luego
se presenta en la consola de salida. Luego, la lectura de todo el contenido
del archivo requiere el uso de una instrucción repetitiva y el tamaño del
archivo, que se puede obtener a través de la siguiente instrucción:

longitud = entrada.available( );

Así como, la clase FileInputStream se utiliza para la lectura, existe la


clase FileOutputStream, la cual, crea un flujo del tipo OutputStream, que
puede ser usado para escribir bytes a un archivo, como señala Schildt
(2017). En este caso, se dispone de los siguientes constructores:

FileOutputStream(String ruta)
FileOutputStream(File archivo)
FileOutputStream(String ruta, boolean agrega)
FileOutputStream(File archivo, boolean agrega)

Al igual que en la clase anterior, el término ruta especifica la dirección


completa al archivo de salida, mientras que el término archivo se refiere a
134 Ángel Pérez

un objeto tipo File que describe al archivo de salida. Los dos constructores
finales permiten agregar bytes a un archivo existente, por lo que incorporan
una variable (agrega) de tipo boolean. Si el archivo especificado no existe,
se genera una excepción del tipo FileNotFoundException. La creación de
objetos es similar al caso anterior, para lo cual se presentan dos ejemplos:

FileOutputStream salida = new FileOutputStream(“prueba.txt”);


File f = new File(“prueba.txt”);
FileOutputStream salida = new FileOutputStream(f);

En este caso, el objeto tipo FileOutputStream creará el archivo de salida


en donde se realizará la operación de escritura. Luego de la creación del
objeto, se puede usar el método write() para escribir, ya sea un byte o un
conjunto de bytes, como muestran los siguientes fragmentos de código:

for (int i=0; i<arreglo.length; i++) salida.write(arreglo[i]);


salida.write(arreglo);

En ambos casos, la variable arreglo es de tipo byte y almacena los


valores a guardar en el archivo de salida. La primera instrucción ejecuta
la escritura de la variable arreglo, elemento por elemento, mientras, la
segunda ejecuta la escritura de todos los elementos de una sola vez.
Si se desea usar ambos flujos, se debe comenzar usando
FileOutputStream, para crear el archivo y guardar los datos. Luego, se crea
el objeto FileInputStream, para abrir el archivo creado y leer los contenidos
almacenados.

1.4. Clases bytearrayinputstream y bytearrayoutputstream


Los flujos vistos en la sección anterior tenían capacidad para la
lectura y escritura de bytes a archivos. Los flujos de esta sección incluyen
capacidades para la lectura y escritura de arreglos de bytes en la memoria,
como señalan Deitel y Deitel (2012). En este orden de ideas, un objeto
de tipo ByteArrayInputStream (una subclase de InputStream) lee de
una matriz de bytes en la memoria, mientras que un objeto de tipo
ByteArrayOutputStream (una subclase de OutputStream) envía las salidas
a una matriz de bytes en la memoria.
Programación II 135

Un uso de esta capacidad es la validación de datos. Un programa puede


introducir una línea entera a la vez desde el flujo de entrada a un arreglo
de bytes. Luego, un método de validación puede escrutar el contenido
de la matriz de bytes y corregir los datos, si es necesario. Finalmente, el
programa puede proceder a la entrada de la matriz de bytes, “sabiendo”
que los datos de entrada están en el formato adecuado. La salida a una
matriz de bytes es una buena forma de tomar ventaja de las poderosas
capacidades de formato de salida de los flujos de Java. Por ejemplo, los
datos pueden almacenarse en una matriz de bytes, que puede salir en un
archivo para preservar el formato.
Aunado a la idea anterior, Schildt (2017), indica que la clase
ByteArrayInputStream tiene dos constructores, como se muestra a
continuación:

ByteArrayInputStream(byte arreglo [ ])
ByteArrayInputStream(byte arreglo [ ], int inicio, int numBytes)

En ambos casos, la variable arreglo representa la fuente de datos. El


segundo constructor incluye las variables inicio y numBytes, que especifican
el subconjunto de bytes a obtener del arreglo original. El siguiente ejemplo
muestra la aplicación de ambos constructores sobre un arreglo de bytes
almacenado en una variable llamada caracteres, la cual guarda las letras
del alfabeto en forma de bytes:

String letras = “abcdefghijklmnopqrstuvwxyz”;


byte caracteres[] = letras.getBytes();
ByteArrayInputStream entrada1 = new ByteArrayInputStream(caract
eres);
ByteArrayInputStream entrada2 = new ByteArrayInputStream(caract
eres, 0, 3);

La variable entrada1 guarda el arreglo entero de bytes de entrada,


mientras que la variable entrada2 guarda los tres primeros elementos
del arreglo. De igual manera, en el caso anterior, Schildt (2017), la clase
ByteArrayOutputStream tiene dos constructores, como se muestra a
continuación:
136 Ángel Pérez

ByteArrayOutputStream()
ByteArrayOutputStream(int numBytes)
Un ejemplo de aplicación de esta clase:
ByteArrayOutputStream f = new ByteArrayOutputStream();

Creado el objeto f y, asumiendo la existencia de un arreglo de bytes


con el nombre de arreglo, se tiene que la escritura de dicho arreglo en el
flujo u objeto f sería:

f.write(buf);

En el caso de que se quiera guardar de manera permanente el arreglo


de bytes, se puede usar un objeto del tipo Fileoutputstream, especificando
el nombre del archivo de salida, como se vió en la sección anterior.

1.5. Filtros
Los flujos vistos en las secciones anteriores se caracterizan por el manejo
(lectura o escritura) de bytes (o de un arreglo de bytes). Un flujo tipo filtro
proporciona una funcionalidad adicional, como la agregación de los bytes
de datos en unidades de tipo primitivo significativas (números, letras, etc.),
según lo señalan Deitel y Deitel (2012). En esta categoría, se encuentran
la clase FilterInputStream, la cual, filtra un objeto tipo InputStream, y
FilterOutputStream, que filtra un objeto tipo OutputStream.
Para Schildt (2017), los flujos tipo filtro son simplemente envoltorios
alrededor de los flujos de entrada o salida subyacentes, que proporcionan
de forma transparente un nivel de funcionalidad ampliada. Se suele
acceder a estos flujos mediante métodos que esperan un flujo genérico,
que es una superclase de los flujos filtrados. Las extensiones típicas son
el almacenamiento en memoria intermedia, la traducción de caracteres y
la traducción de datos en bruto. Los constructores correspondientes se
muestran a continuación:

FilterOutputStream(OutputStream os)
FilterInputStream(InputStream is)
Programación II 137

En cada caso, el argumento corresponde a un objeto tipo OutputStream


(en el caso del flujo FilterOutputStream) o un objeto tipo InputStream
(en el caso del flujo FilterInputStream). Luego, en cada caso, se parte
de un flujo basado en bytes, el cual se conecta a un flujo tipo filtro (una
subclase del flujo base) para transformar los bytes en datos básicos o de
tipo primitivo.

1.6. Clases Datainputstream y Dataoutputstream


Con los flujos descritos en las secciones anteriores, la lectura de los
datos en forma de bytes sin procesar es rápida, pero rudimentaria y
requiere un procesamiento adicional para convertir dichos datos a una
representación que pueda ser entendida por personas. Normalmente, los
programas leen los datos como agregados de bytes, que forman datos de
tipo entero, real, caracteres alfabéticos y así sucesivamente, por lo que es
necesario utilizar flujos tipo filtro, como los descritos en la sección anterior.
Los programas en Java pueden usar varias clases para datos de entrada
y salida en forma agregada. Eck, (2019), señala que el paquete java.io
incluye una clase de flujo de bytes, DataOutputStream, que puede utilizarse
para escribir valores de datos a flujos en formato de número binario. En
este orden, DataOutputStream tiene métodos como writeDouble() para la
salida de valores de tipo double, writeInt() para la salida de valores de tipo
int, y así sucesivamente.
Además, se puede envolver cualquier objeto tipo OutputStream en
un objeto tipo DataOutputStream, para poder utilizar métodos de salida
de nivel superior en él. Por ejemplo, si el objeto fuenteByte es de tipo
OutputStream, se podría escribir la siguiente instrucción, para aplicar una
envoltura tipo DataOutputStream a dicho objeto:

DataOutputStream fuenteData = new DataOutputStream(fuenteByte);

Consecuentemente, para la lectura de datos creados al escribir en un


DataOutputStream, java.io proporciona la clase DataInputStream. Se puede
envolver cualquier objeto tipo InputStream en un objeto DataInputStream,
para proporcionarle la capacidad de leer datos de varios tipos del flujo de
bytes. Los métodos en el flujo DataInputStream para leer datos binarios
se llaman readDouble(), readInt(), y así sucesivamente.
Los datos escritos por un DataOutputStream están garantizados en un
formato que puede ser leído por un DataInputStream. Esto es cierto incluso
138 Ángel Pérez

si el flujo de datos se crea en un tipo de computador y se lee en otro tipo


de computador. La compatibilidad multiplataforma de los datos binarios es
un aspecto importante de la independencia de plataforma de Java.
Continuando con las explicaciones, Deitel, y Deitel, (2012) muestra
la equivalencia en las operaciones de entrada y salida, al presentar los
métodos de lectura y escritura existentes en cada flujo:

• Lectura: readBoolean, readByte, readChar, readDouble, readFloat,


readFully (para conjuntos de bytes), readInt, readLong, readShort,
readUnsignedByte, readUnsignedShort, readUTF (para leer caracteres
Unicode codificados por Java) y skipBytes.

• Escritura: writeBoolean, writeByte, writeBytes, writeChar, writeChars


(para cadenas Unicode), writeDouble, writeFloat, writeInt, writeLong,
writeShort y writeUTF (para producir texto modificado para Unicode).

Para Schildt (2017), el DataInputStream es considerado, el complemento


de DataOuputStream. Estos flujos facilitan el almacenamiento de datos
binarios, como números enteros o valores de punto flotante, en un archivo.
Considerando lo expuesto el siguiente fragmento de código muestra el uso
combinado de DataOutputStream y DataInputStream:

//Primero, se efectúa la escritura de datos a un archivo llamado Prueba.


dat
try (DataOutputStream salida = new DataOutputStream(new
FileOutputStream(“Prueba.dat”)))
{
salida.writeDouble(98.6); //Escribe un valor tipo double
salida. writeInt(1000); //Escribe un valor tipo int
salida. writeBoolean(true); //Escribe un valor lógico o booleano
}
//Ahora, se efectúa la lectura de datos guardados en el archivo llamado
Prueba.dat
try (DataInputStream entrada = new DataInputStream (new
FileInputStream(“Prueba.dat”)))
Programación II 139

{
double real = entrada.readDouble( ); //Lee un valor tipo double
int entero = entrada.readInt( ); // Lee un valor tipo int
boolean logico = entrada.readBoolean( ); // Lee un valor lógico o
booleano
}

Como se ve en el ejemplo anterior, estos flujos escriben y leen datos


de un archivo, el cual es el tema que se va a ver en la sección 2 de
este libro. Primero, se efectúa la escritura de los datos, usando el método
correspondiente al tipo de datos de cada valor; luego, se efectúa la lectura
de los mismos, en el mismo orden como fueron escritos. El manejo de los
flujos presentados anteriormente involucra el manejo de excepciones, por
lo que hay que tomar las siguientes acciones:

• En el método main(), agregar la cláusula throws IOException


• Las instrucciones referidas a los flujos y sus métodos deben ser
incluidas en un bloque try { } (que debe tener su correspondiente bloque
catch { })

1.7. Clase PrintStream


Los objetos o clases predefinidas, mencionadas en la definición de
flujos (System.out, System.err) forman parte de la clase PrintStream, de
acuerdo a lo indicado por Lemay y Perkins (2007), mientras System.in
forma parte de la clase InputStream. A diferencia de los casos presentados
anteriormente, la clase PrintStream, que pertenece a los flujos de salida,
no tiene equivalente en los flujos de entrada. Debido a que este flujo
suele estar conectado a un dispositivo de salida de pantalla de algún tipo,
proporciona una implementación del método flush(). También proporciona
los métodos close() y write(), así como una lista de opciones para la salida
de los tipos primitivos y cadenas de Java:

public void write(int b);


public void write(byte[] memoriaIntermedia, int desplazamiento, int
length);
140 Ángel Pérez

public void flush();


public void close();
public void print(Object o);
public void print(String s);
public void print(char[] memoriaIntermedia);
public void print(char c);
public void print(int i);
public void print(long l);
public void print(float f);
public void print(double d);
public void print(boolean b);
public void println(Object o);
public void println(String s);
public void println(char[] memoriaIntermedia);
public void println(char c);
public void println(int i);
public void println(long l);
public void println(float f);
public void println(double d);
public void println(boolean b);
public void println(); // Imprime una línea en blanco

Al respecto, el PrintStream también puede ser envuelto alrededor de


cualquier flujo de salida, al igual que una clase tipo filtro, como ilustran las
siguientes instrucciones:

PrintStream salida = PrintStream(new FileOutputStream(“Prueba”));

salida.println(“Esta es la primera línea en el archivo Prueba.”);


Programación II 141

Si se proporciona un segundo argumento al constructor de la clase


PrintStream, debe ser uno de tipo booleano, para especificar si el flujo
debe descargarse automáticamente. Si es true, se ejecuta el método
flush(), después de que se escribe cada caracter (o para la forma de tres
argumentos de write(), después de que se ha escrito un grupo entero
de caracteres). A continuación, se muestra un ejemplo básico que lee de
la entrada estándar (System.in), a través de un objeto tipo DataInput e
imprime lo leído en la salida estándar, línea por línea:
import java.io.*; // Importa las clases del paquete java.io
public class EjemploDataInput {
public static void main(String args[]) {
DataInput entrada = new DataInputStream(System.in);
String linea;
try { while ((linea = entrada.readLine()) != null)
System.out.println(linea); //Imprime lo leído en la salida estándar,
línea por línea
} catch (IOException ignored) { }
}
}

Como indican Downey y Mayfield (2016), System es una clase que


proporciona métodos relacionados con el “sistema” o entorno en el que
se ejecutan los programas. También proporciona System.out, que es un
valor especial que proporciona métodos para mostrar la salida, incluyendo
println. Se puede usar System.out.println para presentar el valor de
System.out, como se muestra a continuación:
System.out.println(System.out);
El resultado de ejecutar esta instrucción es:
java.io.PrintStream@685d72cd

Esta salida muestra lo que se indicó al principio: System.out es de tipo


PrintStream, que se define en un paquete llamado java.io. Los números y
letras después del signo @ son la dirección de System.out, representado
como un número hexadecimal (base 16). La dirección de un valor es su
142 Ángel Pérez

ubicación en la memoria de la computadora, que puede ser diferente en


diferentes computadoras. En este ejemplo, la dirección es 685d72cd, pero
si se ejecuta el mismo código otra vez, se podría obtener un valor diferente.

2. ARCHIVOS
En esta parte se hablará de la relevancia de los archivos o ficheros en
programación Java, ya que estos contienen un código fuente, lo cual, está
representado en secuencias de caracteres, organizados en líneas de igual
o diferente longitud, donde el programador los representa de forma lógica
y ordenada.

2.1. Definición
Un archivo es un contenedor para guardar datos e información en
un sistema informático. Los datos guardados en archivos son datos
persistentes (se encuentran disponibles, aunque la ejecución del programa
haya terminado). De manera más formal, Joyanes, y Zahonero, (2008),
definen un archivo como una colección de registros relacionados entre
sí, con aspectos en común y organizados para un propósito específico.
Por ejemplo, un archivo escolar contiene un conjunto de registros de los
estudiantes de una clase en particular (identificación, apellidos, nombres,
entre otros datos).
En un archivo, los datos deben estar organizados de tal modo que
puedan ser recuperados fácilmente, actualizados o borrados y almacenados
de nuevo en el archivo con todos los cambios realizados. Hay diferentes
tipos de archivos como archivos de texto, archivos de datos, archivos de
directorio, archivos binarios y gráficos, y estos diferentes tipos de archivos
almacenan diferentes tipos de información.
Por otra parte, en un computador, los archivos pueden almacenarse en
unidades de discos duros, memorias portátiles u otros tipos de dispositivos
de almacenamiento. Los datos contenidos en los archivos pueden variar
desde información generada por el sistema hasta información especificada
por el usuario. Las operaciones básicas que se pueden realizar en un
archivo son:
• Creación de un nuevo archivo
• Modificación de datos o atributos de archivos
• Lectura de datos del archivo
Programación II 143

• Abrir el archivo para que los contenidos estén disponibles para


otros programas
• Escritura de datos en el archivo
• Cierre o finalización de una operación de archivo

2.2. Clase File


En Java, para ubicar un archivo o directorio, según explica Eck,
(2019), hay dos tipos de nombres de ruta (path names): nombres de
ruta absolutos y nombres de ruta relativos. Un nombre de ruta absoluto
identifica de manera única un archivo entre todos los archivos disponibles
en la computadora. Contiene información completa sobre el directorio en
el que se encuentra el archivo y el nombre del mismo. Un nombre de
ruta relativo indica a la computadora cómo localizar el archivo a partir del
directorio actual.
Para evitar algunos de los problemas causados por las diferencias en
los nombres de las rutas entre las plataformas, Java tiene la clase java.
io.File. Un objeto perteneciente a esta clase representa un nombre de
archivo, en lugar de un archivo como tal. Los directorios son tratados en la
misma forma como los archivos, así que un objeto File puede representar
un directorio, como puede representar un archivo. Un objeto File tiene
un constructor, “new File(String)”, que crea un objeto File a partir de un
nombre de ruta.
El nombre puede ser un nombre simple, un camino relativo o un
camino absoluto. Por ejemplo, new File(“data.dat”) crea un objeto File
que se refiere a un archivo llamado data.dat, en el directorio actual. Otro
constructor, “new File(File,String)”, tiene dos parámetros. El primero es un
objeto File que se refiere a un directorio. El segundo puede ser el nombre
del archivo en ese directorio o una ruta relativa de ese directorio al archivo.
En este orden de ideas, para usar la clase File, hay que colocar, al
principio de la clase, una declaración import, como se especifica a
continuación:

import java.io.File; // Importa la clase File, del paquete java.io.

Dentro de la clase, se crea un objeto tipo File y se especifica el


nombre del archivo o del directorio, a través de uno de los constructores
mencionados arriba (en este ejemplo, el primero):
144 Ángel Pérez

File objArchivo = new File(“archivo.txt”);

En la creación, se especifica el nombre del archivo y su ruta de


acceso, si es requerida. En el ejemplo anterior, objArchivo es un objeto
tipo File, asociado a un archivo denominado archivo.txt, ubicado en el
mismo directorio o carpeta donde se encuentra el programa que contiene
la instrucción anterior. La clase File tiene métodos para crear y obtener
información sobre archivos y directorios, como se muestra a continuación:

Cuadro 10

Métodos de la clase File

Método Tipo Descripción


canRead() Boolean Comprueba si el archivo es legible o no
Comprueba si el archivo se puede
canWrite() Boolean
escribir o no
createNewFile() Boolean Crea un archivo vacío
delete() Boolean Elimina un archivo
exists() Boolean Comprueba si el archivo existe
Comprueba si el nombre corresponde
isFile() Boolean
a un archivo
Comprueba si el nombre corresponde a
isDirectory() Boolean
un directorio
Comprueba si el nombre corresponde
isAbsolute() Boolean a una ruta absoluta a un archivo o
directorio
getName() String Retorna el nombre del archivo
Devuelve la ruta de acceso absoluta del
getAbsolutePath() String
archivo
Retorna el nombre del directorio origen
getParent() String
del archivo o directorio especificado
Devuelve el tamaño del archivo en
length() Long
bytes
Devuelve una lista de los archivos en el
list() String[]
directorio
mkdir() Boolean Crea un directorio
Fuente: Deitel y Deitel (2012)
Programación II 145

De la tabla anterior, se utiliza el método createNewFile() para crear un


archivo. Este método devuelve un valor booleano: true si el archivo se ha
creado correctamente, y false si el archivo ya existe. A continuación, se
presenta un ejemplo, en donde se efectúa la creación de un nuevo archivo,
llamado archivo.txt.

import java.io.File; // Importa la clase File, para el manejo de archivos


import java.io.IOException; // Importa la clase IOException para
manejar errores

public class CreaArchivo {


public static void main(String[] args) {
try {
File objetoArchivo = new File(“archivo.txt”);
// Invoca el método createNewFile() para crear un archivo
if (objetoArchivo.createNewFile()) {
System.out.println(“Archivo creado: “ + objetoArchivo.getName());
} else {
System.out.println(“Archivo ya existe.”);
}
} catch (IOException e) {
System.out.println(“Ocurrio un error.”);
e.printStackTrace();
}
}
}

El método se incluye en un bloque try... catch. Esto es necesario, porque


lanza una excepción del tipo IOException, si ocurre un error (el archivo
no pudo ser creado por alguna razón), el cual se imprime en el bloque
catch que sigue al try. El ejemplo anterior, presentó un solo método de los
mostrados en la tabla mencionada con anterioridad, pero dichos métodos
146 Ángel Pérez

pueden ser usados de manera conjunta, para obtener información sobre


archivos (o sobre directorios) y sus atributos, como se muestra en el
siguiente ejemplo.

// Clase File utilizada para obtener información sobre archivos y


directorios.
import java.io.File;
import java.util.Scanner;

public class DemostracionFile


{
public static void main( String[] args )
{
Scanner entrada = new Scanner( System.in );

System.out.print( “Escriba aqui el nombre del archivo o directorio:


“ );
analizarRuta( entrada.nextLine() );
} // Fin de main

// Muestra información acerca del archivo especificado por el usuario


public static void analizarRuta( String ruta ) {
// Crea un objeto File con base en la entrada del usuario
File nombre = new File (ruta);
if ( nombre.exists() ) // si existe el nombre, muestra información
sobre él
{
// Muestra información del archivo (o directorio)
System.out.printf(
“%s%s\n%s\n%s\n%s\n%s%s\n%s%s\n%s%s\n%s%s\
n%s%s”,
Programación II 147

nombre.getName(), “ existe”,
( nombre.isFile() ? “es un archivo” : “no es un archivo” ),
( nombre.isDirectory() ? “es un directorio” : “no es un directorio”
),
( nombre.isAbsolute() ? “es ruta absoluta” : “no es ruta
absoluta” ),
“Ultima modificacion: “, nombre.lastModified(),
“Tamanio: “, nombre.length(),
“Ruta: “, nombre.getPath(),
“Ruta absoluta: “, nombre.getAbsolutePath(),
“Padre: “, nombre.getParent() );
if ( nombre.isDirectory() ) // muestra el listado del directorio {
String[] directorio = nombre.list();
System.out.println( “\n\nContenido del directorio:\n” );
for ( String nombreDirectorio : directorio )
System.out.printf( “%s\n”, nombreDirectorio );
} // Fin de if
} // Fin de if exterior
else // no es archivo o directorio, muestra mensaje de error
{
System.out.printf( “%s %s”, ruta, “no existe.” );
} // Fin de else
} // Fin del método analizarRuta
} // Fin de la clase DemostracionFile

Para crear un archivo en un directorio determinado (requiere permiso),


se especifica la ruta del archivo y se usan barras invertidas dobles “\” (para
Windows), como se muestra a continuación:

File objArch = new File(“C:\\Users\\MyName\\archivo.txt”);


148 Ángel Pérez

File objArch = new File(“C:\\Users\\MyName\\archivo.txt”);


El siguiente ejemplo, muestra la creación de un archivo, así como la
ruta en donde se encuentra ubicado.
import java.io.File;
import java.io.IOException;
public class CreaArchDir {
public static void main(String[] args) {
try {
File objArch = new File(“archivo.txt”);
if (objArch.createNewFile()) {
System.out.println(“Archivo creado: “ + objArch.getName());
System.out.println(“Ruta absoluta: “ + objArch.getAbsolutePath());
} else {
System.out.println(“Archivo ya existe.”);
}
} catch (IOException e) {
System.out.println(“Ocurrio un error.”);
e.printStackTrace();
}
}
}

Esta clase puede ser combinada con las clases disponibles en la API
(Application Program Interface, o Interfaz de Programas de Aplicación)
de Java que pueden utilizarse para leer y escribir archivos: FileReader,
BufferedReader, Scanner, FileInputStream, FileWriter, BufferedWriter,
FileOutputStream, entre otros, para desarrollar aplicaciones que manejen
bytes, caracteres o agregados de los anteriores, según sea el caso.
En algunos de los ejemplos anteriores, se ha hecho uso de las siguientes
palabras reservadas: try, catch. Las mismas sirven para efectuar el manejo
de las excepciones. Según Eck (2019), una excepción es una anomalía en el
flujo normal de control del programa. El término se utiliza con preferencia a
Programación II 149

“error” porque en algunos casos, una excepción puede no ser considerada


como un error en sí. A veces, se puede pensar en una excepción como
otra forma de organizar un programa. El término “excepción” se utiliza
para referirse al tipo de evento que se puede manejar con la declaración
try... catch.
Las excepciones en Java se representan como objetos de tipo Exception.
Las excepciones reales son normalmente definidas por subclases de
Exception. Las diferentes subclases representan diferentes tipos de
excepciones. Cuando se produce una excepción, se dice que la misma es
“lanzada” (thrown).
Por ejemplo, la instrucción Integer.parseInt(str) lanza una excepción
del tipo NumberFormatException cuando el valor de str es ilegal. Cuando
se lanza una excepción, es posible “atrapar” (catch) la excepción y evitar
que falle el programa. Esto se hace con una instrucción try..catch. En forma
simplificada, la sintaxis para un bloque try..catch puede ser:

try {
< instrucciones-1 > //Grupo de instrucciones
}
catch ( < nombre-de-excepción > <nombre-de-variable> ) {
< instrucciones-2 >//Grupo de instrucciones
}

El <nombre-de-excepción> podría ser: NumberFormatException,


IOException, IllegalArgumentException, entre otras. Cuando el computador
ingresa al bloque try...catch, ejecuta <instrucciones-1>, las instrucciones
dentro de la parte del try. Si no se produce ninguna excepción durante
dicha ejecución, entonces el computador se salta la parte del catch y
procede con el resto del programa.
Sin embargo, si una excepción del tipo <nombre-de-excepción> se
produce durante la ejecución de <instrucciones-1>, el computador salta
inmediatamente del punto donde se produce la excepción a la parte del catch
y ejecuta <instrucciones-2>, saltándose cualquier declaración restante en
<instrucciones-1>. Sólo se captura un tipo de excepción; si se produce
algún otro tipo de excepción durante la ejecución de <instrucciones-1>, el
programa abortará la ejecución.
150 Ángel Pérez

Durante la ejecución de <instrucciones-2>, <nombre-de-variable>


representa el objeto de excepción, que se puede imprimir, por ejemplo.
Dicho objeto contiene información sobre la causa de la excepción. Esto
incluye un mensaje de error, que se mostrará si se imprime <nombre-de-
variable. Después del final de la parte catch, la computadora procede con
el resto del programa; la excepción ha sido capturada y manejada y el
programa no abortará la ejecución. Es de notar que las llaves, { y }, son
parte de la sintaxis del bloque try...catch. Se requieren incluso si sólo hay
una instrucción entre las llaves.
En el ejemplo de código anterior, se hizo uso de un bloque try...catch,
de la siguiente manera:

try {
File objArch = new File(“archivo.txt”);
if (objArch.createNewFile()) {
System.out.println(“Archivo creado: “ + objArch.getName());
System.out.println(“Ruta absoluta: “ + objArch.getAbsolutePath());
} else {
System.out.println(“Archivo ya existe.”);
}
}

En la parte del try de dicho ejemplo, se crea un archivo y se muestran


dos características del mismo (nombre y ruta de ubicación), si el archivo es
nuevo, o se presenta un mensaje que indica la existencia del archivo, en
caso contrario. Lo anterior, es lo que se va a ejecutar, en caso de no haber
excepción alguna. De existir, se va a ejecutar esta otra parte del ejemplo,
la parte del catch:

catch (IOException e) {
System.out.println(“Ocurrio un error.”);
e.printStackTrace();
}
Programación II 151

En esta parte del ejemplo, se espera que ocurra una excepción del
tipo IOException y el nombre del objeto es e. Si ocurre una excepción de
este tipo, se presenta un mensaje que indica la ocurrencia de un error y, a
través de la variable e, se imprime el contenido de la pila (stack), mediante
el método printStackTrace().
Si la excepción que se genera es distinta de IOException, el programa
terminará su ejecución. En este caso, se puede tomar nota de la excepción
generada e incluir otro bloque catch, a continuación del especificado
anteriormente, para abarcar esta nueva situación; de esta manera, se
pueden cubrir las posibles excepciones que se puedan presentar y, de esta
manera, hacer que el programa sea más robusto.

2.3. Tipos de Archivos


Para Schildt (2017), la información de un ordenador está almacenada
en lo que se llaman archivos, estos generalmente, están formados por un
nombre, un punto y una extensión. Así, el nombre servirá para distinguir
unos archivos de otros y la extensión para atribuir unas propiedades
concretas. En este orden de ideas, estas propiedades relacionadas o “tipo
de archivo” vienen dadas por las letras que conforman la extensión, y en
estos, se pueden archivar de acuerdo al programa que este asociado.

2.3.1. Por el tipo de acceso a los registros de datos


Como se indicó en la Definición anterior, en un computador, los archivos
pueden almacenarse en unidades de disco duro, memoria portátil u
otros tipos de dispositivos de almacenamiento. En base a eso, Joyanes
y Zahonero (2008) establecen que, según las características del soporte
empleado y el modo en que se han organizado los registros dentro del
archivo, se consideran los siguientes tipos de acceso a los registros de un
archivo:

• Acceso secuencial

• Acceso directo

• Secuencial indexado
152 Ángel Pérez

El acceso secuencial implica el acceso a un archivo, según el orden de


almacenamiento de sus registros, uno tras otro. El acceso directo implica
el acceso a un registro determinado, sin que ello implique la consulta de
los registros precedentes, para lo cual se hace de una clave o índice. Este
tipo de acceso sólo es posible con soportes direccionales (disco duro, disco
compacto, memoria portátil, entre otros). El último caso, es la combinación
de los dos anteriores, de tal manera que, si busca un registro particular, se
abre el archivo en modo directo; si se desea procesar todos los registros,
el archivo se abre en modo secuencial.

2.3.2. Por el tipo de dato almacenado


En la sección de flujos referenciada por varios autores, se vieron los
casos de

• Fileinputstream (se utiliza para la lectura de datos orientados a bytes


de un archivo) y Fileoutputstream (que puede ser usado para escribir bytes
a un archivo)

• DataOutputStream (que puede utilizarse para escribir valores de


datos a un archivo) y DataInputStream (proporciona la capacidad de leer
datos de varios tipos)

• Filtros: FilterInputStream y FilterOutputStream, que sirven para


transformar los bytes en datos básicos o de tipo primitivo, para lectura o
escritura.

Luego, los archivos pueden ser clasificados, según el tipo de flujo usado
en:

• Binario

• exto

• Serializado (guarda datos tipo Objeto)


Programación II 153

2.3.3. Por la operación


Al principio de la sección de Flujos, se indicó que se pueden identificar,
como operaciones básicas, la escritura de datos (para realizar el
almacenamiento de datos) y la lectura (para realizar consultas), por lo
tanto, los archivos pueden ser clasificados como:

• Entrada

• Salida

2.4. Archivo Fuente


Los archivos con extensión .java son los archivos fuente del proyecto
en Java. Estos son archivos de texto ASCII (American Standard Code for
Information Interchange), desarrollados y entendidos por el programador.
En Java, un archivo fuente es oficialmente llamado una unidad de
compilación, de acuerdo a lo indicado por Schildt (2018). El mismo es un
archivo tipo texto que contiene (entre otras cosas) una o más definiciones
de clases.
En este oren de ideas, el compilador de Java requiere que un archivo
fuente use la extensión .java. Todo el código debe residir dentro de una
clase. Por convención, el nombre de la clase principal debe coincidir con el
nombre del archivo que almacena el programa. Adicionalmente, hay que
asegurar que la combinación de mayúsculas y minúsculas del nombre del
archivo coincida con el nombre de la clase.
Lemay y Perkins (2007), establecen que, si todo el código estuviera
en un solo archivo, se podría estar violando una de las convenciones del
compilador: sólo una clase debería estar ubicada en cada archivo fuente
de Java. En realidad, el compilador sólo se preocupa de que cada clase
pública esté en un archivo separado (aunque sigue siendo un buen estilo
usar archivos separados para cada clase). Las interfaces, como las clases,
se declaran en los archivos fuentes, una interfaz en un archivo. Al igual
que las clases, también se compilan en archivos .class. Las interfaces
complementan y amplían el poder de las clases, y las dos pueden ser
tratadas casi exactamente igual.
Cuando el programa compila sin errores, se genera un archivo con el
mismo nombre del archivo fuente y la extensión .class, y se ubica en el
mismo directorio que dicho archivo, como señalan Lemay y Perkins (2007).
154 Ángel Pérez

Este es el archivo de código binario (bytecodes) de Java. Se puede ejecutar


ese archivo bytecode usando el intérprete de Java. En el JDK, el intérprete
de Java se llama simplemente java.

2.5. Archivos de Entrada


Como se indicó al principio de la sección de Flujos, Joyanes, y Zahonero,
(2008), indican que una de las operaciones básicas es la lectura de datos
de un archivo (para realizar consultas), la cual involucra la apertura del
mismo:

File objArch = new File(“archivo.txt”);

Luego, se crea un objeto tipo Scanner, al cual se asocia el archivo


abierto:
Scanner objLector = new Scanner(objArch);
Cada línea o registro del archivo es obtenida (leida) a través del método
nextLine() de la clase Scanner y guardada en una variable (dato), para ser
presentada en pantalla posteriormente.

String dato = objLector.nextLine();

El proceso continúa, hasta alcanzar el final del archivo, luego de lo cual


se procede al cierre del archivo, a través del objeto Scanner.

objLector.close();

A continuación, se presenta el código fuente completo que implementa


la descripción dada anteriormente

import java.io.File; // Importa la clase File


import java.io.FileNotFoundException; // Importa esta clase para
manejar errores
import java.util.Scanner; // Importa la clase Scanner para leer archivos
tipo texto
Programación II 155

public class LeeArchivo {


public static void main(String[] args) {
try {
File objArch = new File(“archivo.txt”); //Apertura del archivo
//Creación del objeto y asociación al archivo aperturado
Scanner objLector = new Scanner(objArch);
//El siguiente proceso de lectura se ejecuta mientras haya líneas en
//el archivo de entrada
while (objLector.hasNextLine()) {
String dato = objLector.nextLine(); //Lectura de un registro en el
archivo
System.out.println(dato);
}
objLector.close(); //Cierre del archivo
} catch (FileNotFoundException e) {
System.out.println(“Ocurrio un error, no se pudo procesar el
archivo.”);
e.printStackTrace();
}
}
}
Luego, un archivo de entrada requiere para su tratamiento, como
mínimo:

• Apertura

• Lectura de datos

• Presentación de los datos leídos (si aplica)

• Cierre
156 Ángel Pérez

Joyanes y Zahonero (2008), establecen que, por razones de integridad


y consistencia de los datos almacenados en un archivo, la última operación
a realizar debe ser el cierre del mismo. Esto garantiza que el archivo no
quede abierto (no se corrompa) y, por lo tanto, no pueda ser procesado en
una próxima oportunidad.

2.6. Archivos de Salida


Joyanes y Zahonero (2008), refieren que una parte del procesamiento
de datos puede requerir la escritura de datos en un archivo de salida. En
el siguiente ejemplo, se usa la clase FileWriter para escribir datos en un
archivo.

import java.io.FileWriter; // Importa la clase FileWriter


import java.io.IOException; // Importa la clase IOException, para el
manejo de errores

public class EscribeEnArch {


public static void main(String[] args) {
try {
FileWriter objEscribe = new FileWriter(“archivo.txt”);
objEscribe.write(“Esta es una linea de texto escrita en un archivo
creado en java”);
objEscribe.close();
System.out.println(“Operacion de escritura al archivo exitosa.”);
} catch (IOException e) {
System.out.println(“Ocurrio un error en el proceso.”);
e.printStackTrace();
}
}
}

A su vez, un archivo de salida requiere para su tratamiento, como


mínimo:
Programación II 157

• Apertura/ creación

• Escritura de datos

• Cierre

Para finalizar, al igual que en el manejo de archivos de entrada, visto


en el punto anterior, por razones de integridad y consistencia de los datos
almacenados en un archivo, la última operación a realizar debe ser el cierre
del mismo. Esto garantiza que el archivo no quede abierto (no se corrompa)
y, por lo tanto, no pueda ser procesado en una próxima oportunidad.
ACTIVIDADES DE AUTOEVALUACIÓN

Nombre: _______________________________
UNIVERSIDAD
Privada
DR. RAFAEL BELLOSO CHACÍN Cédula: __________________ Sección: ______

Responda los siguientes enunciados. Valor: 1 pts.

1. Por el tipo de acceso a los registros de datos, un archivo puede ser


de tipo: _________, _____________ y ______________.

2. Un archivo de entrada requiere para su tratamiento, como mínimo:


____________, _____________, ________________________________
y ________________.

3. Clase utilizada para obtener información sobre archivos y


directorios: __________.

4. Método que retorna el nombre del archivo:


____________________________.

5. ________________ es una abstracción que produce o consume


información.

6. Revise y corrija el siguiente código:


import java.io.Fil; //Complete el nombre de la librería requerida
public class _______________________ {
public static void main(String args[]){
try{
FileOutputStream salida = new (“prueba.txt”);
String cadena = “Actividad de autoevaluación para flujos.”;
byte b[ ] = cadena.getBytes(); // Convierte la cadena en un
arreglo de bytes
salida.(b);
salida.close();
System.out.println(“Escritura completada.”);
}catch(Exception e){System.out.println(e);}
}
}
7. Elabore un programa que lea el contenido del archivo creado en el
ejercicio anterior y presente el resultado de la lectura en consola.

8. Use el método write() para escribir lo siguiente:


“Esta es una linea de texto”
“Fue escrita en un archivo creado en java”
“Para lo cual se uso la clase FileWriter”
BIBLIOGRAFÍA Y OTRAS FUENTES DE CONSULTA

Aguirre, S. (2020). Programación orientada a objetos. Clases,


objetos y métodos. Vol. 1. Buenos Aires. Six Ediciones.

Arrollo, C. (2019). Clases-Construcción de objetos. Vol II. Buenos


Aires. Six Ediciones.

Brassard, G. y Bratley, P. (1997). “Fundamentos de Algoritmia”.


Madrid. Prentice Hall.

Cairó, O. y Guardati, S. (2006). “Estructuras de datos. Tercera


edición”. México. McGraw-Hill/Interamericana Editores S.A. de C.V.

Cosmina, I. (2018). “Java for Absolute Beginners: Learn to Program


the Fundamentals the Java 9+ Way”. Edinburgh, UK. Apress
Media, LLC.

Deitel, P. y Deitel, H. (2012). “Java How to Program, Ninth Edition”.


Boston. Prentice Hall.

Downey, A. y Mayfield Ch. (2016). “Think Java. How to Think Like a


Computer Scientist”. Needham. Green Tea Press.

Eck, D. (2019). “Introduction to Programming Using Java”. New


York. Hobart and William Smith Colleges.

Jaime, A. (2002). “Estructuras de datos y algoritmos”. Bogotá.


Pearson Educación de Colombia Ltda.

Joyanes, L. y Zahonero, I. (2008). “Estructuras de datos en Java”.


Madrid. McGraw-Hill/Interamericana de España S.A.U.

Lemay, L. y Perkins, Ch. (2007). “Teach yourself Java in 21 days”.


Indianapolis. Sams Publishing.

Lewis, J. y Chase, J. (2006). “Estructuras de datos con Java. Diseño


de estructuras y algoritmos”. Madrid. Pearson Educación S.A.

Márquez, L. (2021). Base de datos orientados a objetos. México.


Editorial Mc Graw Hill.
Schildt, H. y Holmes, J. (2004). “El arte de programar en Java”. México.
McGraw-Hill/Interamericana Editores S.A. de C.V.

Schildt, H. (2017). “Java. The Complete Reference. Tenth Edition”.


New York. McGraw-Hill Education

Schildt, H. (2018). “Java: A Beginner’s Guide, Eighth Edition”. New


York. Oracle Press. McGraw-Hill.

Sierra, K. y Bates, B. (2005). “Head First Java”. California. O’Reilly


Media. Inc.

Wu, C. Thomas. (2001). “Introducción a la programación orientada


a objetos”. Madrid. McGraw-Hill/Interamericana de España S.A.U.

REVISTA ELECTRÓNICA

Pimienta, R, & Aguilar, G, & Ramírez, M, & Gallegos, G (2014). Métodos


de programación segura en Java para aplicaciones móviles en
Android. CIENCIA Ergo-Sum. Revista Científica Multidisciplinaria de
Prospectiva, 21(3), 243-248. Disponible en: https://www.redalyc.org/
articulo.oa?id=10432355009

También podría gustarte