Sunteți pe pagina 1din 47

CHAPTER 1 Abstraction and Modeling

Como seres humanos, estamos inundados de información todos los días de


nuestras vidas. Incluso si pudiéramos apague temporalmente todas las fuentes
de "e-información" que constantemente nos bombardean con correos
electrónicos, correos de voz, transmisiones de noticias y cosas por el estilo,
solo nuestros cinco sentidos recopilan millones de bits de información por día
solo de nuestro entorno. Sin embargo, logramos darle sentido a toda esta
información, generalmente sin sentirnos abrumados. Nuestros cerebros
naturalmente simplifican los detalles de todo lo que observamos para que estos
detalles sean manejables a través de un proceso conocido como abstracción.
En este capítulo, aprenderás
• Cómo la abstracción sirve para simplificar nuestra visión del mundo

• Cómo organizamos nuestro conocimiento jerárquicamente para minimizar la


cantidad de información
que tenemos que hacer malabares mentalmente en cualquier momento dado
• La relevancia de la abstracción para el desarrollo de software

• Los desafíos inherentes que enfrentamos como desarrolladores de software al


intentar modelar una situación de palabras reales en el software
Simplificación a través de la abstracción

Tómese un momento para mirar alrededor de la habitación en la que está


leyendo este libro. Al principio, puede pensar que realmente no hay muchas
cosas que observar: algunos muebles, lámparas, tal vez algunas plantas, obras
de arte, incluso algunas otras personas o mascotas. Tal vez hay una ventana
para mirar que abre el mundo exterior a la observación.

Ahora mira otra vez. Por cada cosa que ve, hay una infinidad de detalles para
observar: su tamaño, su color, su propósito previsto, los componentes con los
que está ensamblado (las patas sobre una mesa, las bombillas en una
lámpara), etc. Además, cada uno de estos componentes a su vez tiene detalles
asociados: el tipo de material utilizado para hacer las patas de la mesa (madera
o metal), el vataje de las bombillas, etc. Ahora factor en tus otros sentidos: el
sonido de alguien roncando (¡Ojalá no mientras lee este libro!), el olor a
palomitas de maíz que sale del horno de microondas al final del pasillo, etc.
Finalmente, piense en todos los detalles ocultos de estos objetos: quién los
fabricó, o cuál es su composición química, molecular o genética.

¡Está claro que la cantidad de información que procesarán nuestros cerebros


es realmente abrumadora! Para la gran mayoría de las personas, esto no
representa un problema, sin embargo, porque somos innatamente hábiles en la
abstracción, un proceso que implica reconocer y centrarse en las
características importantes de una situación u objeto, y filtrar o ignorar todos los
detalles no esenciales.

Un ejemplo familiar de una abstracción es un mapa de ruta. Como abstracción,


un mapa de ruta representa las características de un área geográfica específica
relevante para alguien que intenta navegar con el mapa, tal vez en automóvil:
carreteras principales y lugares de interés, obstáculos tales como grandes
extensiones de agua, etc. Por necesidad, un mapa de ruta no puede incluir
todos los edificios, árboles, letreros, vallas publicitarias, semáforos,
restaurantes de comida rápida, etc. que existen físicamente en el mundo real.
Si lo hiciera, sería tan abarrotado que sería prácticamente inutilizable; ninguna
de las características importantes se destacaría. Comparar una hoja de ruta
con un mapa topográfico, un mapa climatológico y un mapa de densidad de
población de la misma región: cada uno abstrae diferentes características del
mundo real, es decir, aquellas relevantes para el usuario previsto del mapa en
cuestión.

Como otro ejemplo, considere un paisaje. Un artista puede mirar el paisaje


desde el perspectiva de colores, texturas y formas como tema prospectivo para
una pintura. Un constructor de viviendas puede mirar el mismo paisaje desde la
perspectiva de dónde puede estar el mejor sitio de construcción en la
propiedad, y evaluar cuántos árboles necesitarán ser despejados para dar paso
a un proyecto de construcción. Un ecologista puede estudiar de cerca la
especie individual de árboles y otra vida vegetal / animal por su biodiversidad,
con el objetivo de preservarlos y protegerlos, mientras que un niño puede
simplemente mirar a todos los árboles en busca del mejor sitio para un árbol
¡casa! Algunos elementos son comunes a las cuatro abstracciones de los
cuatro observadores del paisaje: los tipos, tamaños y ubicaciones de los
árboles, por ejemplo, mientras que otros no son relevantes para todas las
abstracciones.
Generalización a través de la abstracción

Si eliminamos suficientes detalles de una abstracción, se vuelve lo


suficientemente genérica como para aplicarse a una amplia gama de
situaciones o instancias específicas. Tales abstracciones genéricas a menudo
pueden ser bastante útiles. Por ejemplo, un diagrama de una celda genérica en
el cuerpo humano, como el de la Figura 1-1, podría incluir solo algunas
características de las estructuras que se encuentran en una celda real.
Este diagrama demasiado simplificado no se ve como una verdadera célula
nerviosa, o una verdadera célula muscular, o un verdadero glóbulo sanguíneo;
y, sin embargo, todavía se puede usar en un entorno educativo para describir
ciertos aspectos de la estructura y la función de todos estos tipos de células, es
decir, aquellas características que los distintos tipos de células tienen en
común.

Cuanto más simple es una abstracción, es decir, cuantas menos características


presenta, más general es, y más versátil es al describir una variedad de
situaciones del mundo real. Cuanto más compleja es una abstracción, más
restrictiva es y, por lo tanto, menos situaciones es útil para describir.
Organizar abstracciones en jerarquías de clasificación
Aunque nuestros cerebros son expertos en abstraer conceptos tales como
mapas de carreteras y paisajes, eso nos deja con cientos de miles, si no
millones, de abstracciones separadas con las que lidiar a lo largo de nuestras
vidas. Para hacer frente a este aspecto de la complejidad, los seres humanos
organizan sistemáticamente la información en categorías de acuerdo con los
criterios establecidos; este proceso se conoce como clasificación.

Por ejemplo, la ciencia clasifica todos los objetos naturales como


pertenecientes al reino animal, vegetal o mineral. Para que un objeto natural se
clasifique como un animal, debe cumplir las siguientes reglas:
• Debe ser un ser vivo.
• Debe ser capaz de movimiento espontáneo.
• Debe ser capaz de una respuesta motora rápida a la estimulación.
Las reglas para lo que constituye una planta, por otro lado, son diferentes:
• Debe ser un ser vivo (lo mismo que para un animal).
• Debe carecer de un sistema nervioso obvio.
• Debe poseer paredes celulares de celulosa.

Dadas reglas claras como estas, ubicar un objeto en la categoría o clase


apropiada es bastante sencillo. Luego podemos "profundizar", especificando
reglas adicionales que diferencian varios tipos de animales, por ejemplo, hasta
que construyamos una jerarquía de abstracciones cada vez más complejas de
arriba a abajo. Un simple ejemplo de una jerarquía de abstracción se muestra
en la Figura 1-2.

Al pensar en una jerarquía de abstracción como la que se muestra en la Figura


1-2, Subir y bajar mentalmente la jerarquía, enfocando automáticamente solo la
capa individual o subconjunto de la jerarquía (conocida como subárbol) que es
importante para nosotros en un momento determinado. Por ejemplo, solo
podemos preocuparnos por los mamíferos y, por lo tanto, podemos centrarnos
en el subárbol de mamíferos, que se muestra en la Figura 1-3, ignorando
temporalmente el resto de la jerarquía.

Al hacerlo, reducimos automáticamente la cantidad de conceptos que


necesitamos mentalmente para hacer malabares en cualquier momento con un
subconjunto manejable de la jerarquía de abstracción general; en nuestro
ejemplo simplista, ahora nos ocupamos de solo 4 conceptos en lugar de los 13
originales. No importa cuán compleja sea una jerarquía de abstracción, no es
necesario que nos abrume si está organizada adecuadamente.
Proponer exactamente qué reglas son necesarias para clasificar correctamente
un objeto dentro de una jerarquía de abstracción no siempre es fácil.
Tomemos, por ejemplo, las reglas que podríamos definir para lo que constituye
un pájaro: a saber, algo que
• Tiene plumas
• Tiene alas
• Pone huevos
• Es capaz de volar

Dadas estas reglas, ni un avestruz ni un pingüino podrían clasificarse como un


pájaro, porque ninguno puede volar (vea la Figura 1-4).

Si intentamos que la regla sea menos restrictiva eliminando la regla del "vuelo",
nos quedamos con
• Tiene plumas
• Tiene alas
• Pone huevos

De acuerdo con este conjunto de reglas, ahora podemos clasificar


correctamente tanto al avestruz como al pingüino como aves, como se muestra
en la Figura 1-5.
Este conjunto de reglas todavía es innecesariamente complicado, ya que
resulta que la regla de "poner huevos" es redundante: ya sea que la
conservemos o eliminemos, no cambia nuestra decisión de lo que constituye un
pájaro frente a un ave que no es de ave. Por lo tanto, simplificamos el conjunto
de reglas una vez más:
• Tiene plumas
• Tiene alas

Sintiéndonos particularmente atrevidos (!), Tratamos de llevar nuestro proceso


de simplificación un paso más allá al eliminar otra regla, definir un pájaro como
algo que
• Tiene alas

Como muestra la Figura 1-6, esta vez hemos ido demasiado lejos: la
abstracción de un pájaro es ahora tan general que incluiríamos aviones,
insectos y todo tipo de otras aves que no sean de la mezcla.
El proceso de definición de reglas para fines de categorización implica "marcar"
solo el conjunto correcto de reglas, no demasiado generales, no demasiado
restrictivas y que no contienen redundancias, para definir la membresía
correcta en una clase en particular.
Abstracción como base para el desarrollo de software

Al establecer los requisitos para un proyecto de desarrollo de sistemas de


información, normalmente comenzamos reuniendo detalles sobre la situación
del mundo real en la que se basará el sistema. Estos detalles suelen ser una
combinación de

• Aquellos que se nos ofrecen explícitamente al entrevistar a los usuarios


previstos del sistema
• Aquellos que de otra manera observamos

Debemos hacer un juicio sobre cuáles de estos detalles son relevantes para el
propósito final del sistema. Esto es esencial, ya que no podemos
automatizarlos todos. Incluir demasiados detalles es complicar demasiado el
sistema resultante, por lo que es mucho más difícil de diseñar, programar,
probar, depurar, documentar, mantener y ampliar en el futuro.

Al igual que con todas las abstracciones, todas nuestras decisiones de


inclusión versus eliminación al construir un sistema de software deben tomarse
dentro del contexto del propósito general y el dominio, o enfoque del tema, del
sistema futuro. Cuando se representa a una persona en un sistema de
software, por ejemplo, ¿es importante el color de sus ojos? ¿Qué hay de su
perfil genético Salario? ¿Aficiones? La respuesta es que cualquiera de estas
características de una persona puede ser relevante o irrelevante, dependiendo
de si el sistema que se desarrollará es
• Sistema de nómina
• Sistema de demografía de marketing
• Base de datos de pacientes del optometrista
• Sistema de seguimiento "más buscado" del FBI

Una vez que hayamos determinado los aspectos esenciales de una situación,
algo que exploraremos en la Parte 2 de este libro, podemos preparar un
modelo de esa situación. El modelado es el proceso por el cual desarrollamos
un patrón para hacer algo. Un modelo para un hogar personalizado, un
diagrama esquemático de un circuito impreso y un cortador de galletas son
todos ejemplos de tales patrones. Como veremos en las partes 2 y 3, un
modelo de objeto de un sistema de software es ese patrón. El modelado y la
abstracción van de la mano, porque un modelo es esencialmente una
representación física o gráfica de una abstracción; antes de que podamos
modelar algo de manera efectiva, debemos haber determinado los detalles
esenciales del tema a modelar.
Reutilización de abstracciones
Al aprender sobre algo nuevo, buscamos automáticamente en nuestro archivo
mental otras abstracciones / modelos que hemos construido y dominado
previamente, para buscar similitudes sobre las que podamos construir. Cuando
aprende a andar en bicicleta de dos ruedas por primera vez, por ejemplo,
puede haber recurrido a las lecciones que aprendió sobre montar un triciclo
cuando era niño (consulte la Figura 1-7). Ambos tienen manillares que se usan
para dirigir; ambos tienen pedales que se utilizan para propulsar la bicicleta
hacia adelante. Aunque las abstracciones no encajaban a la perfección (una
bicicleta de dos ruedas presentaba el nuevo desafío de tener que equilibrarse)
había suficiente similitud que le permitía aprovechar la experiencia de dirección
y pedaleo que ya dominaba, y centrarse en aprendiendo la nueva habilidad de
cómo balancearse sobre dos ruedas.

Esta técnica de comparar características para encontrar una abstracción que


sea lo suficientemente similar como para reutilizarla con éxito se conoce como
coincidencia de patrones y reutilización. Como veremos en el Capítulo 12, la
reutilización de patrones es una técnica importante para el desarrollo de
software orientado a objetos, porque nos evita tener que reinventar la rueda
con cada nuevo proyecto. Si podemos reutilizar una abstracción o modelo de
un proyecto anterior, podemos centrarnos en aquellos aspectos del nuevo
proyecto que difieren de los anteriores, obteniendo una gran cantidad de
productividad en el proceso.
Desafíos inherentes

A pesar de que la abstracción es un proceso tan natural para los seres


humanos, desarrollar un modelo apropiado para un sistema de software es
quizás el aspecto más difícil de la ingeniería de software, porque

• Hay un número ilimitado de posibilidades. La abstracción es, hasta cierto


punto, en el ojo del observador: es casi seguro que varios observadores
diferentes que trabajen de forma independiente lleguen a diferentes modelos.
¿De quién es el mejor? ¡Han surgido argumentos apasionados!

• Para complicar aún más las cosas, prácticamente nunca hay un solo modelo
"mejor" o "correcto", solo modelos "mejores" o "peores" en relación con el
problema a resolver. La misma situación se puede modelar en una variedad de
formas diferentes, igualmente válidas. Cuando lleguemos a hacer un poco de
modelado en la Parte 2 de este libro, veremos una cantidad de abstracciones
alternativas válidas para nuestro estudio de caso del Sistema de Registro de
Estudiantes (SRS) que se presentó al final de la Introducción.

• Tenga en cuenta, sin embargo, que existe un modelo incorrecto: a saber, uno
que tergiversa la situación del mundo real (por ejemplo, modelar una persona
que tiene dos tipos de sangre diferentes).

• No existe una prueba de ácido para determinar si un modelo ha capturado


adecuadamente todos los requisitos de un usuario. La última evidencia de si
una abstracción fue o no apropiada es en qué tan exitoso resulta el sistema de
software resultante. No queremos esperar hasta el final de un proyecto antes
de descubrir que nos hemos descarriado. Debido a esto, es fundamental que
aprendamos formas de comunicar nuestro modelo de manera concisa y sin
ambigüedades a las siguientes personas:

• Los usuarios futuros previstos de nuestra aplicación, para que puedan


proporcionar un control de cordura para nuestra comprensión del problema a
resolver antes de emprender el desarrollo de software

• Nuestros compañeros ingenieros de software, para que los miembros del


equipo puedan compartir una visión común de lo que debemos construir
colaborativamente

A pesar de todos estos desafíos, es fundamental que la abstracción inicial sea


"correcta" antes de comenzar a construir un sistema. Reparar errores en la
abstracción una vez que un sistema es modelado, diseñado, codificado,
documentado y sometido a pruebas de aceptación es mucho más costoso (por
órdenes de magnitud) que corregir la abstracción cuando todavía es un brillo en
el ojo del equipo del proyecto. ¡Esto no implica que una abstracción sea rígida
sino todo lo contrario! El arte y la ciencia del modelado de objetos, cuando se
aplica correctamente, produce un modelo que es lo suficientemente flexible
como para soportar una amplia variedad de cambios funcionales. Además, las
propiedades especiales de los objetos se prestan a soluciones de software
flexibles, como aprenderá durante el resto del libro. Sin embargo, en igualdad
de condiciones, nos gustaría aprovechar esta flexibilidad para expandir las
capacidades de un sistema a lo largo del tiempo, en lugar de reparar los
errores.
¿Qué se necesita para ser un modelador de objetos exitoso?

Proponer una abstracción apropiada como base para un modelo de sistema de


software requiere

• Información sobre el dominio del problema: idealmente, podremos recurrir a


nuestras propias experiencias del mundo real, como la experiencia anterior o
actual como estudiante, que serán útiles para determinar los requisitos del
SRS.

• Creatividad: debemos ser capaces de pensar "fuera de la caja", en caso de


que los futuros usuarios que estamos entrevistando hayan estado inmersos en
el área problemática durante tanto tiempo que no vean las innovaciones que
podrían hacerse.

• Buenas habilidades para escuchar: Estas serán útiles ya que los futuros
usuarios del sistema describen cómo hacen su trabajo actualmente, o cómo
prevén hacer su trabajo en el futuro, con la ayuda del sistema que estamos a
punto de desarrollar.

• Buenas habilidades de observación: las acciones a menudo hablan más que


las palabras. Solo observando a los usuarios que realizan su trabajo diario,
podemos recoger un detalle esencial que han olvidado mencionar porque lo
hacen de manera tan rutinaria que se ha convertido en un hábito. Pero todo
esto no es suficiente. También necesitamos
• Un proceso organizado para determinar lo que la abstracción debería ser. Si
seguimos una lista de verificación comprobada de los pasos para producir un
modelo, entonces reducimos enormemente la probabilidad de que omitamos
alguna característica importante o descuidemos un requisito crítico.

• Una forma de comunicar el modelo resultante de manera concisa y sin


ambigüedades a nuestros compañeros desarrolladores de software y a los
usuarios previstos de nuestra aplicación. Si bien es posible describir una
abstracción en texto narrativo, una imagen vale más que mil palabras, por lo
que el lenguaje con el que comunicamos un modelo suele ser una notación
gráfica. A lo largo de este libro, nos enfocaremos en la notación del lenguaje de
modelado unificado (UML, ver Figura 1-8) como nuestro modelo de lenguaje de
comunicación (aprenderá los conceptos básicos de UML en los capítulos 10 y
11). Piense en un modelo gráfico como modelo de la aplicación de software
que se construirá.

• Idealmente, también tendremos una herramienta de software para ayudarnos


a automatizar el proceso de producción de dicho modelo.

La Parte 2 de este libro cubre estos tres aspectos del proceso de modelado, la
notación y la herramienta en detalle.
Resumen
En este capítulo, has aprendido que

• La abstracción es una técnica fundamental que las personas usan para


percibir el mundo.

• Desarrollar una abstracción del problema para ser automatizado es un primer


paso necesario para todo el desarrollo de software.

• Organizamos naturalmente la información en jerarquías de clasificación


basadas en reglas que estructuramos cuidadosamente, de modo que no son
demasiado generales ni demasiado restrictivas.
• A menudo reutilizamos abstracciones cuando intentamos modelar un nuevo
concepto.

• Producir una abstracción de un sistema a construir, conocido como modelo,


es de alguna manera una segunda naturaleza para nosotros y,
paradójicamente, es una de las cosas más difíciles que tienen que hacer los
desarrolladores de software en el ciclo de vida de un proyecto de sistemas de
información. También es uno de los más importantes.
INFORMACIÓN IMPORTANTE RELATIVA A LOS EJERCICIOS DE FIN DE
EJERCICIO

Incluí ejercicios al final de cada capítulo para ayudarlo en su experiencia de


aprendizaje.

• Muchos de los ejercicios se han diseñado como preguntas abiertas y


estimulantes, adecuadas como tareas para el hogar en un entorno académico.
Por lo tanto, no hay respuestas simples de "talla única para todos" disponibles
para tales ejercicios.

• Algunos ejercicios incluyen programación práctica y están etiquetados de la


siguiente manera: [Codificación].
• Se le anima a hacer preguntas sobre los ejercicios, o sobre Java en general, a
través del grupo de discusión en línea Foros Apress en
http://forums.apress.com, donde obtiene el beneficio de las ideas de otros
lectores, así como de Apresar autores / expertos.
Algunos conceptos básicos de Java

Los objetivos de este libro no son solo enseñarle objetos y modelado de


objetos, sino también ilustrar cómo los objetos se traducen en una aplicación
de software en funcionamiento. Entonces, antes de sumergirnos en lo básico
de los objetos en el Capítulo 3, pasaremos algún tiempo cómodos con los
fundamentos de Java, ya que este es el lenguaje de programación utilizado
para ilustrar los conceptos de objetos a medida que se introducen en el libro.

Los objetos son "neutros en el lenguaje", por lo que lo que aprenderá


conceptualmente sobre los objetos en la Parte 1 de este libro, y sobre el
modelado de objetos en la Parte 2, podría aplicarse igualmente a Java, C ++ o
C #, o un todavía lenguaje orientado a objetos inventado (OO). Elegí Java
específicamente por las numerosas ventajas que este elegante lenguaje de
programación OO nos ofrece como desarrolladores de software, que
exploraremos en este capítulo. En este capítulo, también aprenderá sobre
• Tipos primitivos de Java, operadores en esos tipos y expresiones formadas
con esos tipos
• La anatomía de un simple programa Java
• La mecánica de compilar y ejecutar tales programas
• La naturaleza estructurada en bloque de Java
• Varios tipos de expresiones Java
• Bucles y otras estructuras de control de flujo
• Imprimir mensajes en la ventana de comandos desde la que se lanzó un
programa, lo que es especialmente útil para probar código a medida que
evoluciona
• Elementos del estilo de programación de Java

Si usted es un programador experto en C, C ++ o C #, encontrará que gran


parte de la sintaxis de Java le resultará muy familiar, y debería poder pasar
rápidamente por este capítulo.
Por qué Java?

Después de aprender qué hace que los objetos "marquen" en la Parte 1 del
libro y cómo modelar una aplicación para aprovechar los objetos en la Parte 2,
estará listo para la gran final: renderizar un modelo de objeto en código para
producir un Estudiante que trabaje Aplicación del Sistema de Registro (SRS) en
la Parte 3. Como se mencionó anteriormente, podríamos avanzar construyendo
el SRS usando cualquier lenguaje de programación OO. ¿Por qué querríamos
usar Java? Siga leyendo y verá rápidamente por qué.
Java es arquitectura neutral

Para ejecutar un programa escrito en un lenguaje compilado


convencionalmente como C o C ++, el código fuente primero debe compilarse
en una forma ejecutable conocida como código binario o código máquina. El
código binario, en esencia, es un patrón de 1s y 0 entendible por la arquitectura
de hardware subyacente de la computadora en la que el programa está
destinado a ejecutarse.

Incluso si el código original C o C ++ está escrito para ser independiente de la


plataforma, es decir, el programa no aprovecha ninguna extensión de lenguaje
específica de la plataforma, como un tipo específico de acceso a archivos o la
manipulación de la interfaz gráfica de usuario (GUI). la versión ejecutable
seguirá estando vinculada a la arquitectura de una plataforma particular y, por
lo tanto, solo se puede ejecutar en esa arquitectura. Es decir, una versión del
programa compilada para una estación de trabajo Sun Solaris no se ejecutará
en una PC con Windows, una versión compilada para una PC con Windows no
se ejecutará en una máquina Linux, y así sucesivamente. Este concepto se
muestra en la Figura 2-1.
Por el contrario, el código fuente de Java no se compila para una plataforma en
particular, sino más bien en un formato intermedio especial conocido como
bytecode, que se dice que es independiente de la plataforma y neutral de la
arquitectura. Es decir, no importa si un programa Java está compilado bajo Sun
Solaris, Windows, Linux o cualquier otro sistema operativo para el que esté
disponible un compilador Java, el bytecode resultante resulta ser el mismo y,
por lo tanto, se puede ejecutar en cualquier computadora para que está
disponible una Máquina Virtual Java (JVM) (específica de la plataforma). Esto
se ilustra en la Figura 2-2.
La JVM es una pieza especial de software que sabe cómo interpretar y ejecutar
bytecode de Java. Es decir, en lugar de que un programa Java se ejecute
directamente bajo el control del sistema operativo de la forma tradicional, la
JVM se ejecuta bajo el control directo del sistema operativo, y nuestro
programa Java a su vez se ejecuta bajo el control de la JVM, como ilustrado en
la Figura 2-3. La JVM en esencia sirve como traductor, traduciendo el
"lenguaje" de byte Java universal en el "lenguaje" de código de máquina que
una computadora en particular puede entender, de la misma manera que un
intérprete humano facilita una discusión entre alguien que habla alemán y
alguien que habla japonés traduciendo sus declaraciones mientras conversan.
La naturaleza interpretada del lenguaje Java tiende a hacerlo un poco más lento,
en general, que los lenguajes compilados porque hay una capa de
procesamiento adicional involucrada cuando se ejecuta una aplicación, como se
ilustra en la Figura 2-3. Para aplicaciones de sistemas de información
tradicionales que involucran a un usuario humano en el circuito, sin embargo, la
diferencia de velocidad es imperceptible; otros factores, como la velocidad de la
red (en el caso de aplicaciones distribuidas), la velocidad de un servidor DBMS
(si se usa una base de datos) y especialmente el "tiempo de reflexión" humana al
responder a la interfaz de usuario de una aplicación, pueden causar cualquier
tiempo de respuesta JVM se retrasa a la palidez por comparación.

Siempre que tenga la JVM adecuada instalada en una plataforma objetivo


determinada, puede transferir códigos de bytes Java de una plataforma a otra
sin recompilar el código fuente original de Java, y aún así podrá ejecutarse. Es
decir, bytecode es transferible a través de plataformas, como se ilustra en la
Figura 2-4.
MIGRATING BYTECODE
Hay dos advertencias importantes a tener en cuenta al migrar bytecode de una
máquina a otra. El primero implica migrar a través de diferentes versiones del
lenguaje Java, incluso si está en la misma plataforma. Java bytecode es, en
teoría, compatible con las versiones más recientes de JVM, lo que significa que
si un programa Java se compila con una versión del lenguaje Java, por
ejemplo, la versión 1.3.1, se ejecutará correctamente con una versión más
nueva de JVM. -ay, versión 1.4.2. Sin embargo, puede haber pequeñas
diferencias en la forma en que una aplicación se ejecuta bajo una versión más
nueva de la JVM, debido a nada más que tal vez que se ha corregido un error
menor en una versión anterior del lenguaje Java.
Sin embargo, incluso cuando se migra a la misma versión de Java en
diferentes arquitecturas de hardware, pueden surgir pequeñas
incompatibilidades. Sun Microsystems es responsable de lanzar nuevas
versiones de Java para solo un puñado de plataformas, en particular, nuevos
"sabores" de Windows, Linux y Sun Solaris (Unix). Depende de otros
proveedores de hardware proporcionar JVM compatibles para sus respectivas
plataformas (por ejemplo, Apple Macintosh y SGI IRIX [Unix]). Me he
encontrado con situaciones en las que un proveedor en partic
ular publica una versión de Java que tiene el mismo número de versión que
una versión Sun de Java, pero donde las características de la versión de ese
proveedor no se comparan con la versión numerada de Sun.

La conclusión es que es mejor recompilar el código fuente de Java para una


determinada plataforma de destino antes de transferir el código de bytes
resultante a esa plataforma, si tiene ese lujo.
Java ofrece "ventanilla única"

Con la mayoría de los lenguajes de programación convencionales, el lenguaje


central no proporciona automáticamente todo lo que necesitará para crear una
aplicación de fortaleza industrial, con una GUI y acceso a un sistema de
administración de bases de datos. Para estas capacidades, normalmente debe
integrar bibliotecas específicas de la plataforma (y, a menudo, específicas del
proveedor) en su aplicación, como se ilustra en la Figura 2-5.

La inclusión de tales llamadas a bibliotecas específicas de la plataforma y del


proveedor dificulta la migración de aplicaciones de una plataforma a otra,
porque no todas las llamadas de biblioteca para una plataforma X dada
necesariamente tienen un equivalente para una plataforma objetivo Y, por
ejemplo, no todos los controles Microsoft ActiveX ( utilizados para compilar GUI
para computadoras con Windows) tienen un equivalente en la biblioteca Motif
de componentes GUI (usados para construir GUI para máquinas Unix), y
viceversa.
Peor aún, el límite entre el código independiente de la plataforma y el
dependiente de la plataforma / proveedor no es realmente tan limpio como se
representa en la Figura 2-5. Las llamadas a estas bibliotecas a menudo se
encuentran dispersas en una aplicación, como se ilustra en la Figura 2-6, lo
que hace que la tarea de migrar una aplicación de una plataforma a otra sea
problemática en el mejor de los casos y prohibitivamente difícil en el peor de los
casos.

Si el límite entre los componentes independientes de la plataforma y los


dependientes de la plataforma de una aplicación es particularmente
desordenado, a veces es el menor de los dos el reescribir la aplicación
completa desde cero en lugar de intentar migrarla. Hay algunos programas
especiales de terceros disponibles llamados emuladores que simularán un
sistema operativo bajo otro, lo que nos permite (en teoría) ejecutar una
aplicación dependiente de la plataforma en otra plataforma incompatible sin
ninguna modificación. Sin embargo, no todas las aplicaciones necesariamente
se ejecutarán correctamente bajo un emulador, y las que normalmente
funcionan con lentitud.

Por el contrario, el lenguaje Java proporciona un amplio conjunto de interfaces


de programación de aplicaciones (API) que proporcionan un medio coherente e
independiente de la plataforma para acceder a todas las funciones subyacentes
del sistema operativo, incluido el renderizado GUI y el acceso al sistema de
gestión de bases de datos. Cubriremos las siguientes API de Java más
adelante, en la Parte 3 del libro:
• java.io: se usa para acceder al sistema de archivos (ver Capítulo 15)
• java.sql: la API de JDBC, utilizada para comunicarse con bases de datos
relacionales de forma independiente del proveedor (ver Capítulo 15)

• java.awt: The Abstract Windowing Toolkit, utilizado para el desarrollo de GUI


(ver Capítulo 16)

• javax.swing: componentes Swing, también utilizados para el desarrollo de GUI


(ver Capítulo 16)

¡Y hay muchos más! Si estas bibliotecas incorporadas de Java se utilizan para


desarrollar aplicaciones Java 100% puras, como se ilustra en la Figura 2-7, el
código Java resultante es realmente portátil entre las plataformas y los
proveedores. Por lo tanto, para migrar una aplicación Java de Windows XP a
Linux, por ejemplo, no es necesario extraer el código (¡ni su cabello!):
Simplemente transfiera el código de bytes de la aplicación a la nueva
plataforma, y siempre que la nueva plataforma de host tiene instalada la
versión adecuada de una JVM específica de la plataforma, entonces está listo
para comenzar.

En la Parte 3 del libro, analizaremos dos técnicas de diseño críticamente


importantes: separación de capas de acceso a datos de modelos y separación
de vistas de modelos, que garantizarán aún más la flexibilidad de proveedores
y plataformas de nuestras aplicaciones.
Java está orientado a objetos desde cero

Antes de que llegaran a la escena los lenguajes de OO más nuevos como


Java, uno de los lenguajes de OO más utilizados era C ++, que en realidad es
una extensión orientada a objetos del lenguaje no OO C. Como tal, C ++
proporciona muchas "puertas traseras" que hacen que sea muy fácil escribir un
código decididamente desprovisto de OO. De hecho, muchos programadores
competentes de C hicieron la transición a C ++ como una mejor C sin aprender
apropiadamente cómo diseñar una aplicación OO, y por lo tanto terminaron
usando C ++ en su mayor parte como un lenguaje procedural (no OO).
Por el contrario, Java está orientado a objetos en su núcleo. Como aprenderá
con más detalle en los capítulos que siguen, casi todo en Java es un objeto:
• Todos los datos, con la excepción de algunos tipos primitivos, se representan
como objetos.

• Todos los componentes básicos de la GUI (ventanas, botones, campos de


entrada de texto, barras de desplazamiento, listas, menús, etc.) son objetos.

• Todas las funciones están asociadas a objetos y se conocen como métodos;


no puede haber funciones de "libre flotación" como las que existen en C / C ++.

• Incluso la función principal utilizada para iniciar una aplicación (en Java se
llama el método principal) ya no está sola, sino que se incluye dentro de una
clase, y las razones por las cuales la exploraremos en profundidad en los
capítulos siguientes.
Debido a esto, Java se presta particularmente bien para escribir aplicaciones
que defienden el paradigma OO. Sin embargo, como señalé en la introducción
de este libro, el mero hecho de utilizar un lenguaje OO de este tipo no garantiza
que las aplicaciones que usted produzca sean fieles a este paradigma. Debe
tener conocimientos tanto sobre cómo diseñar una aplicación desde cero para
hacer el mejor uso de los objetos como sobre cómo aplicar el idioma
correctamente, que son los propósitos primarios de este libro.
La práctica hace la perfección

Los diseñadores de lenguaje de Java en Sun Microsystems pudieron


aprovechar las lecciones aprendidas de otros lenguajes de programación OO
que le precedieron. Pidieron prestada las mejores características de C ++,
Eiffel, Ada y Smalltalk, y luego agregaron algunas capacidades y funciones que
no se encuentran en esos idiomas. Por el contrario, se eliminaron las
características que habían demostrado ser más problemáticas en los idiomas
anteriores.

Además, a partir de la versión 5.0 (aka1.5) de Java, varias características de un


lenguaje más reciente, el C # de Microsoft, fueron adaptadas en Java.

Esto no quiere decir que Java es un lenguaje "perfecto" (¡no es el lenguaje!),


Sino simplemente que ha realizado algunas mejoras significativas en muchos
de los idiomas que lo han precedido.
Java es un estándar abierto
¿Qué significado tiene para nosotros el hecho de que Java es un estándar
abierto como desarrolladores de software? Por un lado, todo el código fuente
detrás de todas las bibliotecas integradas de Java está disponible para que lo
estudiemos y usemos como base para nuestro propio diseño de software.

Además, la industria de TI en general ha adoptado rápidamente Java porque


no se han excluido proveedores externos, como suele ser el caso con una
tecnología patentada / "cerrada".
Todo lo contrario: Sun Microsystems ha alentado a otros proveedores a adoptar
el estándar Java e incorporar la capacidad de Java en sus productos al permitir
que los proveedores participen en el avance de las especificaciones de la
tecnología Java a través del Java Community Process de Sun.

Y, si las razones anteriores todavía no son lo suficientemente convincentes


como para convencerlo de los méritos de Java. .
¡Java es gratis!

Sun Microsystems se ha asegurado de que Java disfrute de una adopción


generalizada, en parte, haciendo que el lenguaje y todas las herramientas
básicas necesarias para desarrollar aplicaciones Java sean gratuitas. El
Apéndice C proporciona información sobre lo que deberá hacer para descargar
el Kit de desarrollo de software Java 2 (SDK) del sitio web de Sun
(http://java.sun.com) para comenzar con el desarrollo de Java. (Proporciono
instrucciones sobre cómo compilar y ejecutar programas Java más adelante en
este capítulo).

El resto de este capítulo presenta alguna sintaxis elemental de Java, que


seguiremos desarrollando en capítulos posteriores.
Un recordatorio sobre el pseudocódigo frente al código real de Java

De vez en cuando uso pequeños trozos de pseudocódigo en los ejemplos del


código en las Partes 1 y 2 de este libro para ocultar detalles lógicos
irrelevantes. Para dejar en claro cuándo estoy usando pseudocódigo en lugar
de código real, uso cursiva en lugar de fuente condensada de SansMono.
Esta es la sintaxis real de Java:
para (int i = 0; i <= 10; i ++) {
Esto es un pseudocódigo:
calcular la calificación para el i-ésimo Estudiante
}

Te recordaré este hecho unas cuantas veces más, para que no olvides y
accidentalmente intentes escribir y compilar un seudocódigo en algún punto del
camino.
Anatomía de un programa simple de Java
La Figura 2-8 muestra una de las aplicaciones Java más simples.
Repasemos los elementos clave de nuestro programa simple.
Comments

Lo primero que vemos en nuestro sencillo programa Java es un comentario


introductorio:
// Este sencillo programa ilustra alguna sintaxis básica de Java.

Java admite tres estilos de comentarios diferentes: tradicional, final de línea y


comentarios de documentación de Java.
Comentarios tradicionales

Los comentarios tradicionales de Java derivan del lenguaje C y comienzan con


una barra inclinada seguida de un asterisco (/ *) y finalizan con un asterisco
seguido de una barra inclinada (* /). Todo lo que se incluye entre estos
delimitadores se trata como un comentario y, por lo tanto, el compilador de
Java lo ignora, sin importar cuántas líneas abarque el comentario.
/ * Este es un comentario tradicional (estilo C). * /

/ * Este es un comentario tradicional multilínea. Esta es una forma práctica de


temporalmente comentar secciones enteras de código sin tener que
eliminarlos.

Desde el momento en que el compilador encuentra el primer "asterisco de


barra" arriba, no importa lo que escribimos aquí; incluso líneas de código
legítimas, como se muestra a continuación, se tratan como líneas de
comentarios y, por lo tanto, son ignoradas por el compilador hasta que se
encuentre la primera combinación de "barra diagonal".
x = y + z;
a = b / c;
j = s + c + f;
*/

/ * A menudo utilizamos asteriscos principales en la segunda a la última línea


de un * comentar simplemente por razones cosméticas, por lo que el
comentario es más visualmente * distinto; pero, estos asteriscos adicionales
son estrictamente cosméticos, solo el * el "asterisco de barra" inicial y la "barra
de asterisco" final son anotadas por * compilador que tiene algún significado.
*/

Tenga en cuenta que no podemos anidar comentarios de bloque; es decir, no


se compilará lo siguiente:
/ * Esto comienza un comentario ...
x = 3;

/ * ¡Vaya! Estamos tratando por error de anidar un SEGUNDO comentario


antes de terminar el PRIMERO!

Esto nos causará problemas de compilación, porque el compilador IGNORARÁ


el inicio de este segundo / comentario interno. ¡Ya estamos en un comentario,
después de todo! - y entonces, tan pronto como tratemos de terminar este
SEGUNDO / comentario interno, el compilador pensará que hemos terminado
el PRIMER / comentario externo en su lugar ... * /
z = 2;
// El compilador se "quejará" en la siguiente línea.
*/

Cuando el compilador alcanza lo que pretendíamos ser el * / finalizador del


comentario "externo" en la última línea del ejemplo de código anterior, se
informarán los siguientes dos errores de compilación:
Comentarios de final de línea

El segundo tipo de comentario de Java deriva de C ++ y se conoce como un


comentario al final de la línea. Usamos una barra doble (//) para anotar el
comienzo de un comentario que termina automáticamente cuando se llega al
final de la línea, como se muestra aquí:
x = y + z; // el texto del comentario continúa hasta el final de la línea ==>
a = b / c;
// Aquí hay un BLOQUE de comentarios secuenciales al final de la línea.
// Esto sirve como una alternativa al uso de comentarios tradicionales
// (/ * ... * /) y es preferido por muchos programadores de Java.
m = n * p;
Comentarios de la documentación de Java

El tercer y último tipo de comentario de Java, los comentarios de la


documentación de Java (también conocidos como comentarios de Javadoc)
pueden analizarse a partir de archivos de código fuente mediante un programa
de utilidad especial de la línea de comandos javadoc (que viene de serie con el
SDK de Java) y utilizarse para generar automáticamente documentación HTML
para una aplicación.

Diferiremos una mirada en profundidad a los comentarios de Javadoc hasta el


Capítulo 13.
La declaración de la clase
Luego viene un contenedor de clases-más propiamente denominado
declaración de clase-de la forma
public class ClassName {
...
}
Por ejemplo:
clase pública SimpleProgram {
...
}

donde llaves {...} encierran el cuerpo de la clase que incluye la lógica principal
del programa junto con otros bloques de construcción opcionales de una clase.

En los capítulos siguientes, aprenderá todo sobre la importancia de las clases


en un lenguaje de programación OO. Por ahora, simplemente tenga en cuenta
que los símbolos public y class son dos de las palabras clave de Java, es decir,
símbolos reservados para usos específicos dentro del lenguaje Java, mientras
que SimpleProgram es un nombre / símbolo que he inventado.
El método principal

Dentro de la declaración de clase SimpleProgram, encontramos el punto de


partida para el programa, llamado el método principal en Java. El método
principal sirve como punto de entrada para un programa Java. Cuando
ejecutamos un programa Java al interpretar su bytecode con una instancia de
la JVM, la JVM llama al método principal para reiniciar nuestra aplicación.

Con aplicaciones triviales como el ejemplo SimpleProgram, toda la lógica


del programa puede estar contenida dentro de este único método
principal. Para aplicaciones más complejas, por otro lado, el método
principal no puede contener toda la lógica de todo el sistema. Aprenderá
a construir una aplicación que trascienda los límites del método principal,
involucrando múltiples archivos de código fuente de Java, un poco más
adelante en el libro.
La primera línea del método, que se muestra aquí
public static void main (String [] args) {

define lo que se conoce como el encabezado de método del método principal, y


debe aparecer exactamente como se muestra (con una pequeña excepción
que explicaré en el Capítulo 13 que tiene que ver con la recepción opcional de
argumentos desde la línea de comandos).
El cuerpo del método de nuestro método principal, encerrado entre llaves {...},
consiste en una única declaración:
System.out.println ("¡Hola!");
que imprime el mensaje

a la ventana de comandos (DOS / Solaris / Linux) desde la que se inicia


nuestro programa. Bien Examine la sintaxis de esta declaración aún más en un
momento, pero por ahora, tenga en cuenta el uso de un punto y coma (;) al final
de la instrucción. Como en C y C ++, los puntos y comas se colocan al final de
todas las sentencias de Java individuales. Las llaves {...}, a su vez, delimitan
bloques de código, cuyo significado analizaré con más detalle un poco más
adelante en este capítulo.
Otras cosas que normalmente haríamos dentro del método principal de un
programa más elaborado incluyen declarar variables, crear objetos y llamar a
otros métodos.

Ahora que hemos analizado la anatomía de un simple programa Java, veamos


cómo un programa es compilado y ejecutado.

Consulte el Apéndice C para obtener detalles sobre cómo instalar el Kit


de desarrollo de software Java (SDK) en su computadora antes de
continuar.
La "Mecánica" de Java

Una vez que hemos ingresado nuestra lógica de programa, como texto, en un
archivo, usando un editor de texto simple (como Windows Notepad o vi) o una
herramienta Java Integrated Development Environment (IDE), primero
debemos compilar el código fuente en bytecode antes podemos ejecutarlo.

Para la gente que acaba de comenzar con la programación de Java,


defiendo usar un editor de texto simple al principio para que no se
distraiga con las características de un IDE específico, y para que el IDE no
haga tanto trabajo para usted que realmente no aprendes lo que está
pasando en el nivel más básico. Sin embargo, si le gusta trabajar con
IDEs, le recomiendo comenzar con una herramienta muy económica
llamada TextPad, disponible en http://www.textpad.com.

Para las personas que son competentes con vi (un editor de Unix / Linux),
tenga en cuenta que hay una versión compatible con Windows / DOS
llamada vim que está disponible sin costo en http: //www.vim.org-
específicamente, puede ser descargado de
http://www.vim.org/download.php.
Una vez que haya dominado el lenguaje Java, puede "graduarse" a un IDE
más complejo si lo desea.

La forma más sencilla de compilar y ejecutar un programa Java es a través de


comandos de línea de comandos, que son los mismos ya sea que
desarrollemos en una computadora Sun Solaris, Linux o Windows.

Para ampliar la capacidad de la ventana del símbolo del sistema y / o cambiar


su tamaño, haga clic con el botón derecho en la barra azul del encabezado de
la ventana y seleccione Propiedades en el menú emergente que aparece, como
se muestra en la siguiente imagen.

En el panel Diseño del cuadro de diálogo que aparece (como se muestra en la


siguiente imagen para una computadora con Windows XP), ajuste las opciones
de Tamaño del búfer de pantalla y Tamaño de ventana como se indica.
Compilación del código fuente de Java en Bytecode

Para compilar el código fuente de Java desde la línea de comandos, usamos el


comando cd, según sea necesario, para navegar en el directorio de trabajo
donde reside nuestro código fuente. Luego escribimos el siguiente comando:
javac sourcecode_filename
por ejemplo:
javac SimpleProgram.java
compilarlo

Si hubiera más de un archivo de código fuente .java en el mismo directorio,


podríamos
enumere los nombres de los archivos que se compilarán, separados por
espacios:
javac Foo.java Bar.java Another.java
o use el carácter comodín (*), por ejemplo:
javac * .java
para compilar varios archivos al mismo tiempo.

Si todo va bien, es decir, si no surgen errores de compilación, aparecerá un


archivo de código de bytes con el nombre de SimpleProgram.class en el mismo
directorio donde reside el archivo de código fuente SimpleProgram.java. Si
surgen errores del compilador, por otro lado, por supuesto debemos corregir
nuestro código fuente e intentar recompilarlo. Consulte la sección titulada
"Errores comunes de compilación" en el Apéndice C para obtener una lista de
algunos de los errores comunes de compilación y cómo resolverlos.
Ejecución de Bytecode
Una vez que un programa ha sido compilado con éxito, ejecutamos la versión
del bytecode a través del comando
java bytecode_filename (tenga en cuenta que OMIT el sufijo .class)
por ejemplo:
java SimpleProgram

Tenga en cuenta que es importante omitir el sufijo .class del nombre de archivo
de código de bytes (que se llama SimpleProgram.class en este caso).
De forma predeterminada, la JVM buscará en su directorio de trabajo
predeterminado junto con el directorio "de inicio" donde se ha instalado el
idioma de Java en su sistema informático para dichos archivos de código de
bytes. Si la JVM encuentra el archivo bytecode especificado, ejecuta su método
principal y ¡su programa está apagado y en funcionamiento!

Si por alguna razón el bytecode que está tratando de ejecutar no está en


ninguna de estas dos ubicaciones predeterminadas, debe informar a la JVM de
los directorios adicionales en los que buscar. Puede hacerlo especificando una
lista de directorios (separados por punto y coma [;] en Windows, o por dos
puntos [:] en Solaris y Linux) después del indicador -cp en el comando java de
la siguiente manera:
java -cp list_of_directory_names_to_be_searched bytecode_filename
Por ejemplo, en DOS / Windows:
java -cp C: \ home \ javastuff; D: \ reference \ workingdir; S: \ foo \ bar \ files
SimpleProgram
o en Solaris / Linux:
java -cp / barkerj / work: / java / examples / ex1 SimpleProgram
Como mínimo, normalmente deseamos que JVM busque en nuestro directorio
de trabajo actual los archivos bytecode. Con la versión 1.4.x de Java y
posterior, esto sucede automáticamente, como se discutió anteriormente; para
las versiones 1.3.x de Java y anteriores, debemos especificar explícitamente un
único período (.), que es (DOS / Linux / Solaris) abreviatura de "el directorio de
trabajo actual", como una entrada de ruta de clase; por ejemplo, para DOS /
Windows:
java -cp. SimpleProgram
o
java -cp.; C: \ home \ javastuff; D: \ reference \ workingdir SimpleProgram
Consulte la sección titulada "Errores comunes de tiempo de ejecución" en el
Apéndice C para obtener una lista de algunos errores comunes de tiempo de
ejecución y cómo resolverlos.
Una mirada detrás de las cámaras a la JVM

Echemos un vistazo más profundo a lo que está sucediendo detrás de las


escenas cuando escribimos
El comando
java SimpleProgram
para ejecutar nuestro programa.

• El comando java hace que se inicie la JVM. Recuerde de una discusión


anterior en este capítulo que la JVM es un intérprete de códigos de bytes.
• La JVM a su vez busca el archivo de código de bytes que hemos nombrado
en el comando (SimpleProgram.class en este caso), buscando en los
directorios enumerados por el –cp bandera, si se proporciona, o en el directorio
de trabajo predeterminado si no se usa -cp.

• Suponiendo que la JVM efectivamente encuentra el archivo de código de


bytes apropiado, carga el bytecode en su memoria (conceptualmente, el
bytecode sirve como un "plug-in" para la JVM).
• Luego, la JVM busca el bytecode que acaba de cargar para la presencia del
encabezado oficial del método principal. Si se encuentra este método, la JVM
lo ejecuta y nuestro programa está fuera de funcionamiento!

Ahora que hemos analizado la mecánica de compilar y ejecutar programas


Java, vamos a explore algunas de las características básicas de sintaxis de
Java con más detalle.
Tipos primitivos

Se dice que Java es un lenguaje de programación fuertemente tipado, en el


que cuando se declara una variable, su tipo también debe ser declarado. Entre
otras cosas, declarar el tipo de una variable le dice al compilador cuánta
memoria asignar para la variable en tiempo de ejecución, y también restringe el
contexto (s) en cuál esa variable se puede utilizar posteriormente en nuestro
programa.

El lenguaje Java define ocho tipos primitivos (los ocho nombres de estos tipos
son Java palabras clave), de la siguiente manera.
Cuatro tipos de datos numéricos enteros:
• byte: entero sin signo de 8 bits
• short: entero de 16 bits con signo
• int: entero de 32 bits con signo
• largo: entero con signo de 64 bits
Dos tipos numéricos de coma flotante:
• flotante: punto flotante de precisión simple de 32 bits
• doble: coma flotante de doble precisión de 64 bits
Además de dos tipos primitivos adicionales:
• char: un solo carácter, almacenado utilizando codificación Unicode de 16 bits
(frente a la codificación ASCII de 8 bits), que permite a Java manejar una
amplia gama de conjuntos de caracteres internacionales.
• booleano: una variable que solo puede asumir uno de dos valores: verdadero
o falso (ambos valores son palabras reservadas en Java). Las variables
booleanas a menudo se usan como indicadores para señalar

si un código debe o no ejecutarse condicionalmente, como en el siguiente


código
retazo:
boolean error = false; // Inicializa la bandera.
// ...
// Más tarde en el programa (pseudocódigo):
si (surge alguna situación de error) {
// Establece el indicador en verdadero para indicar que ha ocurrido un error.
error = verdadero;
}
// ...
// Aún más tarde en el programa:
// Prueba el valor de la bandera.
if (error == verdadero) {
// Pseudocódigo.
tomar acción correctiva ...
}

Hablaremos específicamente sobre la sintaxis de la instrucción if, uno de varios


tipos diferentes de declaraciones de control de flujo de Java, un poco más
adelante en este capítulo.
Un recordatorio importante: si desea intentar compilar cualquiera de los
fragmentos de código Java que encuentra en el libro, recuerde que (a) el
pseudocódigo (en cursiva) no se compilará, y (b) todo el código debe,
como mínimo , se incluye dentro de un método principal, que a su vez
debe incluirse dentro de una declaración de clase, como se ilustra en la
Figura 2-8.
Variables
Antes de que una variable pueda usarse en un programa Java, el tipo y el
nombre de la variable deben declararse al compilador de Java, por ejemplo:
recuento int;
La asignación de un valor a una variable se realiza mediante el operador de
asignación de Java, =. Una declaración de asignación consiste en un nombre
de variable (previamente declarado) a la izquierda de = y una expresión que
evalúa el tipo apropiado a la derecha de = (cubriremos varios tipos de
expresiones Java más adelante en el capítulo). Por ejemplo:
int count = 1;
total = total + 4.0; // Aquí, suponemos que el total fue declarado como un doble
// variable anterior en el programa.

precio = costo + (a + b) / longitud; // Una vez más suponemos que todas las
variables fueron
// correctamente declarado anteriormente en el programa.

Se puede proporcionar un valor inicial cuando una variable se declara por


primera vez:
recuento int = 3;

o una variable se puede declarar en una declaración, luego se le asigna un


valor en una declaración separada más adelante en el programa:
doble total;
// código intermedio ... detalles omitidos
total = total + 4.0;

Se puede asignar un valor a una variable booleana utilizando los literales


verdadero o falso:
booleano terminado;
// ...
terminado = verdadero;
Se puede asignar un valor literal a una variable de tipo char al adjuntar el valor
(un solo
Carácter Unicode) en comillas simples:
char c = 'A';

El uso de comillas dobles ("...") está reservado para asignar valores literales a
las variables de cadena, un tipo distinto que se tratará más adelante en este
capítulo. Lo siguiente no se compilaría en Java:

char c = "A"; // Debemos usar comillas simples cuando asignamos valores a


variables char.
Convenciones de nomenclatura variable
Cuando se debaten nombres de variables Java, hay dos aspectos a considerar:

• Primero, ¿un nombre particular es considerado válido por el compilador de


Java?

• Segundo, ¿un nombre válido particular se adhiere a la convención de


nomenclatura que ha sido adoptada por la comunidad de programación OO en
todos los idiomas?
Los nombres válidos de las variables en Java deben comenzar con un carácter
alfabético, un guión bajo o un signo de dólar (cuyo uso no se recomienda, ya
que el compilador los utiliza al generar código) y pueden contener cualquiera
de estos caracteres más dígitos numéricos. No se permiten otros caracteres en
nombres de variable.
The following are all valid variable names in Java:
int simple; // starts with alphabetic character
int _under; // starts with underscore

int more$money_is_2much; // may contain dollar signs, and/or underscores,


and/or
// digits, and/or alphabetic characters
while these are invalid:
int 1bad; // inappropriate starting character
int number#sign; // contains invalid character
int foo-bar; // ditto
int plus+sign; // ditto
int x@y; // ditto
int dot.notation; // ditto

Dicho esto, la convención que se observa en toda la comunidad de


programación de OO es formar nombres variables utilizando principalmente
caracteres alfabéticos, evitando el uso de guiones bajos, y además adherirse a
un estilo conocido como carcasa de camello. Con la carcasa de camello, la
primera letra de un nombre de variable está en minúscula, la primera letra de
cada palabra concatenada posterior en el nombre de la variable está en
mayúscula y el resto de los caracteres en minúsculas. Todos los siguientes
nombres de variables son válidos y convencionales:
int grade;
double averageGrade;
String myPetRat;
boolean weAreFinished;

Recuerde que, como se mencionó anteriormente, las palabras clave de Java no


se pueden usar como nombres de variables. Lo siguiente no se compilará,
porque public es una palabra clave Java:
int public;
De hecho, el compilador generaría los siguientes dos mensajes de error:
not a statement
int public;
^
';' expected
int public;
^
Inicialización variable

En Java, a las variables no se les asigna necesariamente un valor inicial


cuando se declaran, pero a todas las variables se les debe asignar un valor
antes de que el valor de la variable se use en una declaración de asignación.
Por ejemplo, en el siguiente fragmento de código, se declaran dos variables int
(eger); un valor inicial se asigna explícitamente a la variable foo, pero no a la
barra variable. Un intento posterior de agregar los valores de las dos variables
en conjunto da como resultado un error de compilación:
int foo;
int bar;
// We're explicitly initializing foo, but not bar.
foo = 3;
foo = foo + bar; // This line won't compile.
El siguiente error de compilación surgiría en la última línea de código:
variable bar might not have been initialized
foo = foo + bar;
^

Para corregir este error, deberíamos asignar un valor explícito a bar, así como
a foo, antes de usarlos en la expresión de suma:
int foo;
int bar;
foo = 3;
// We're now initializing BOTH variables explicitly.
bar = 7;
foo = foo + bar; // This line will now compile properly.
El tipo de cadena
Veremos otro tipo de Java más importante en este capítulo: el tipo de cadena.
Una cadena representa una secuencia de cero o más caracteres Unicode.

El símbolo String comienza con una "S" mayúscula, mientras que los nombres
de los tipos primitivos se expresan en minúsculas: int, float, boolean, etc. Esta
diferencia de mayúsculas es deliberada y la cadena obligatoria (minúsculas) no
funcionará como un tipo :
cadena s = "foo"; // Esto no se compilará.
Aquí está el mensaje de error:
cannot find symbol
symbol: string

(Explicaré el significado de la capitalización de String como un tipo a su debido


tiempo).

Hay varias formas de crear e inicializar una variable String. La manera más fácil
y más comúnmente utilizada es declarar una variable de tipo String y asignar
un valor a la variable mediante un literal de cadena. Un literal de cadena es
cualquier texto entre comillas dobles, incluso si consta de un solo carácter:
String name = "Steve"; // Note the use of double quotes, regardless of the
String shortString = "A"; // length, when we're assigning a literal value
// to a String variable.

Dos enfoques comúnmente utilizados para inicializar una variable de cadena


con un valor de marcador de posición temporal son los siguientes:
• Asignando una cadena vacía, representada por dos marcas de comillas
dobles consecutivas:
String s = "";
• Asignando el valor nulo, que es una palabra clave de Java que se usa para
indicar que todavía no se le ha asignado a un String un valor "real" (como
aprenderá más adelante en el libro, usaremos la palabra clave null en este
mismo moda para referencias de objetos en general):
String s = null;

El operador de signo más (+) se usa normalmente para la suma aritmética,


pero cuando se utiliza junto con Cadenas, representa una concatenación de
cadenas. Cualquier número de valores de cadena se puede concatenar con el
operador +, como lo ilustra el siguiente fragmento de código:
String x = "foo";
String y = "bar";
String z = x + y + "!"; // z assumes the value "foobar!" (x and y's values are
// unaffected)

Aprenderá sobre algunas de las muchas otras operaciones que se pueden


realizar con o en Strings, junto con información sobre su naturaleza OO, en el
Capítulo 13.

Java es un lenguaje sensible a mayúsculas y minúsculas. Es decir, el uso de


mayúsculas y minúsculas en Java es deliberado y obligatorio, por ejemplo:

• Los nombres de variables que se escriben de la misma manera pero que


difieren en el uso del caso representan diferentes variables:
// These are two DIFFERENT variables as far as the Java compiler
// is concerned.
int x; // lowercase
int X; // uppercase

• Todas las palabras clave se representan en minúsculas: public, class, int,


boolean, etc. No se vuelva "creativo" sobre la capitalización de estos, ya que el
compilador se opondrá violentamente, a menudo con mensajes de error de
compilación ininteligibles, como en el siguiente ejemplo, donde la palabra
reservada está incorrectamente en mayúscula:
// The reserved word 'for' should be lowercase.
For (int i = 0; i < 3; i++) {
que a su vez produce el siguiente error de compilador aparentemente extraño:
'.class' expected
For (int i = 0; i < 3; i++) {
• El nombre del método principal es minúscula.
• Como se mencionó anteriormente, el tipo de cadena comienza con una "S"
mayúscula.
Expresiones de Java

Java es un lenguaje orientado a la expresión. Una expresión simple en Java es


cualquiera
• Una constante: 7, falsa
• Un literal char (acter) encerrado entre comillas simples: 'A', '3'
• Un literal de cadena entre comillas dobles: "foo", "Java"
• El nombre de cualquier variable declarada correctamente: myString, x
• Dos de los tipos de expresión anteriores que se combinan con uno de los
Java operadores binarios (discutidos en detalle más adelante en este capítulo):
x+2

• Cualquiera de los tipos de expresión anteriores modificados por uno de los


Java unarios operadores (discutidos en detalle más adelante en este capítulo): i
++

• Cualquiera de los tipos de expresión anteriores encerrados entre paréntesis:


(x + 2) más algunos otros tipos de expresiones que tienen que ver con objetos
sobre los que aprenderá más adelante en el libro.

Las expresiones de complejidad arbitraria se pueden ensamblar a partir de los


diferentes tipos de expresión anidando paréntesis, por ejemplo: ((((4 / x) + y) *
7) + z).
Operadores aritméticos

El lenguaje Java proporciona una serie de operadores aritméticos básicos,


como se muestra en la Tabla 2-1.

Los operadores + y - también se pueden usar como operadores unarios para


indicar números positivos o negativos: -3.7, +42.

Además del operador de asignación simple, =, hay una serie de operadores de


asignación de compuestos especializados, que combinan la asignación de
variables con una operación aritmética, como se muestra en la Tabla 2-2.

Los últimos dos operadores aritméticos que veremos son los operadores de
incremento unificado (++) y decremento (-), que se utilizan para aumentar o
disminuir el valor de una variable int en 1 o de un punto flotante (flotante) ,
doble) valor por 1.0. Se los conoce como operadores unarios porque se aplican
a una sola variable, mientras que los operadores binarios combinan los valores
de dos expresiones como se discutió anteriormente. Los operadores de
incremento y decremento unarios también se pueden aplicar a variables char
para avanzar o retroceder una posición de carácter en la secuencia de
clasificación Unicode. Por ejemplo, en el siguiente fragmento de código, el valor
de la variable c se incrementará de 'e' a 'f':
char c = 'e';
c++; // c will be incremented from 'e' to 'f'.

Los operadores de incremento y decremento se pueden usar en forma de


prefijo o de posfijo. Si el operador se coloca antes de la variable en la que está
operando (modo de prefijo), el incremento o decremento de esa variable se
realiza antes de que el valor actualizado de la variable se use en cualquier
asignación hecha a través de esa declaración. Por ejemplo, considere el
siguiente fragmento de código, que usa el operador de incremento de prefijo
(++). Supongamos que a y b han sido previamente declaradas como variables
int en nuestro programa:
a = 1;
b = ++a;

Después de que se hayan ejecutado las líneas de código anteriores, el valor de


la variable a será 2, al igual que el valor de la variable b. Esto se debe a que,
en la segunda línea de código, el incremento de la variable a (de 1 a 2) ocurre
antes de que el valor de a se asigne a la variable b. Por lo tanto, la única línea
de código
b = ++a;
es lógicamente equivalente a las siguientes dos líneas de código:
a = a + 1; // Increment a's value first ...
b = a; // ... THEN use its value.

Por otro lado, si el operador de incremento / decremento se coloca después de


la variable en la que está operando (modo postfija), el incremento o
decremento ocurre después de que el valor original de la variable se usa en
cualquier asignación hecha a través de esa declaración. Miremos el mismo
fragmento de código con el operador de incremento escrito de forma postfija
a = 1;
b = a++;
Después de que se hayan ejecutado las líneas de código anteriores, el valor de
la variable b será 1, mientras que el valor de la variable a será 2. Esto se debe
a que, en la segunda línea de código, el incremento de la variable a (de 1 a 2)
ocurre después de que el valor de a se asigna a la variable b. Por lo tanto, la
única línea de código
b = a++;
es lógicamente equivalente a las siguientes dos líneas de código:
b = a; // Use a's value first ...
a = a + 1; // ... THEN increment its value.

Aquí hay un ejemplo un poco más complejo; Lea el comentario que lo


acompaña para asegurarse de que pueda ver cómo a x se le asignará el valor
10:
int y = 2;
int z = 4;
int x = y++ * ++z; // x will be assigned the value 10, because z will be
// incremented from 4 to 5 BEFORE its value is used in the
// multiplication expression, whereas y will remain at 2 until
// AFTER its value is used in the multiplication expression.

Como verá en un momento, los operadores de incremento y decremento se


usan comúnmente junto con los bucles.
Operadores relacionales y lógicos
Una expresión lógica compara dos expresiones (simples o complejas) exp1 y
exp2 de una manera específica, resolviendo a un valor booleano de verdadero
o falso.

Para crear expresiones lógicas, Java proporciona los operadores relacionales


que se muestran en la Tabla 2-3.
Además de los operadores relacionales, Java proporciona operadores lógicos
que se pueden usar para combinar / modificar expresiones lógicas. Los
operadores lógicos más comúnmente utilizados se enumeran en la Tabla 2-4.

Aquí hay un ejemplo que usa el operador lógico "y" para programar el
compuesto lógico expresión "si x es mayor que 2.0 y y no es igual a 4.0":
if ((x > 2.0) && (y != 4.0)) { ... }

Las expresiones lógicas se utilizan con mayor frecuencia en las estructuras de


control de flujo, que se analizarán más adelante en este capítulo.
Evaluar expresiones y precedencia del operador

Como se mencionó anteriormente en el capítulo, las expresiones de


complejidad arbitraria se pueden construir acodando paréntesis anidados, por
ejemplo, (((8 * (y + z)) + y) * x). El compilador generalmente evalúa tales
expresiones desde el paréntesis más interno al más externo, de izquierda a
derecha. Suponiendo que x, y y z se declaran e inicializan como se muestra
aquí
int x = 1;
int y = 2;
int z = 3;
luego la expresión en el lado derecho de la siguiente declaración de asignación
int answer = ((8 * (y + z)) + y) * x;
serían evaluados pieza por pieza de la siguiente manera:
((8 * (y + z)) + y) * x
((8 * 5) + y) * x
(40 + y) * x
42 * x
42
En ausencia de paréntesis, ciertos operadores tienen prioridad sobre otros en
términos de cuándo se aplicarán al evaluar una expresión. Por ejemplo, la
multiplicación o división se realiza antes de la suma o la resta. La precedencia
del operador puede ser explícitamente alterada mediante el uso de paréntesis;
las operaciones realizadas dentro de paréntesis tienen prioridad sobre las
operaciones fuera de paréntesis. Considere el siguiente fragmento de código:
int j = 2 + 3 * 4; // j will be assigned the value 14
int k = (2 + 3) * 4; // k will be assigned the value 20

En la primera línea de código, que no usa paréntesis, la operación de


multiplicación tiene prioridad sobre la operación de suma, por lo que la
expresión general se evalúa como el valor 2 + 12 = 14; es como si hubiéramos
escrito explícitamente 2 + (3 * 4) sin tener que hacerlo.
En la segunda línea de código, los paréntesis se colocan explícitamente
alrededor de la operación 2 + 3 para que la operación de adición se realice
primero, y la suma resultante se multiplicará por 4 para un valor de expresión
global de 5 * 4 = 20.
Volviendo a un ejemplo anterior
if ((x > 2.0) && (y != 4.0)) { ... }

tenga en cuenta que los operadores> y! = tienen prioridad sobre el operador


&&, de modo que podríamos eliminar los paréntesis anidados de la siguiente
manera:
if (x > 2.0 && y != 4.0) { ... }

Sin embargo, los paréntesis adicionales ciertamente no duelen, y de hecho se


puede argumentar que hacen que la intención de la expresión sea más clara.
El tipo de una expresión

El tipo de expresión es el tipo de Java del valor al que la expresión finalmente


evalúa.
Por ejemplo, dado el fragmento de código
double x = 3.0;
double y = 2.0;
if ((x > 2.0) && (y != 4.0)) { ... }

la expresión (x> 2.0) && (y! = 4.0) se evalúa como verdadera y, por lo tanto, se
dice que la expresión (x> 2.0) && (y! = 4.0) es una expresión de tipo booleano.
Y, en el siguiente fragmento de código
int x = 1;
int y = 2;
int z = 3;
int answer = ((8 * (y + z)) + y) * x;

la expresión ((8 * (y + z)) + y) * x evalúa a 42, y por lo tanto la expresión ((8 * (y


+ z)) + y) * x se dice que es un tipo int (eger) expresión.
Conversiones automáticas de tipo y conversión explícita

Java admite la conversión automática de tipos. Esto significa que si tratamos


de asignar un valor a una variable
// Pseudocode.
x = expression;

y la expresión en el lado derecho de la declaración de asignación evalúa a un


tipo diferente que el tipo con el que se declaró la variable en el lado izquierdo
de la instrucción de asignación, Java convertirá automáticamente el valor de la
expresión de la derecha para que coincida con el tipo de x, pero solo si la
precisión no se perderá al hacerlo. Esto se comprende mejor al observar un
ejemplo:
int x;
double y;
y = 2.7;
x = y; // We're trying to assign a double value to an int variable.

En el fragmento de código anterior, estamos intentando asignar el valor doble


de y, 2.7, a x, que se declara como un int. Si esta asignación tuviera lugar, la
parte fraccional de y se truncaría, y x terminaría con un valor entero de 2. Esto
representa una pérdida de precisión, también conocida como conversión de
estrechamiento.

Un compilador C o C ++ permitirá esta asignación, truncando silenciosamente


el valor. En lugar de suponer que esto es lo que pretendemos hacer, sin
embargo, el compilador de Java generará un error en la última línea de código
de la siguiente manera:
possible loss of precision
found: double
required: int

Para indicar al compilador de Java que estamos dispuestos a aceptar la


pérdida de precisión, debemos realizar un lanzamiento explícito, es decir,
debemos preceder a la expresión cuyo valor se va a convertir con el tipo de
destino, entre paréntesis:
// Pseudocode.
x = (type) expression;

En otras palabras, tendríamos que reescribir la última línea del ejemplo anterior
de la siguiente manera para que el compilador de Java permita la asignación
de un valor de coma flotante más preciso a una variable entera menos precisa:
int x;
double y;
y = 2.7;
x = (int) y; // This will compile now, because we have explicitly
// informed the compiler that we WANT a
// narrowing conversion to occur.

Por supuesto, si tuviéramos que invertir la dirección de la asignación,


asignando el valor int de la variable x a la variable doble y, el compilador de
Java no tendría ningún problema con la asignación:
int x;
double y;
x = 2;
y = x; // Assign a less precise int value to a double variable that is capable of
// handling more precision – this is fine as is.

En este caso particular, estamos asignando un valor de menos precisión-2 a


una variable capaz de más precisión; y terminará con el valor de 2.0. Esto se
conoce como una conversión de ampliación. Dichas conversiones se realizan
automáticamente en Java, y no es necesario que se emitan explícitamente.
Tenga en cuenta que existe una idiosincrasia con respecto a la asignación de
valores constantes a variables de tipo float en Java; la siguiente declaración no
se compilará:
float y = 3.5; // This won't compile!

porque un valor constante numérico con un componente fraccionario como 3.5


es tratado automáticamente por Java como un valor doble más preciso, por lo
que el compilador lo verá como una conversión de estrechamiento y se negará
a llevar a cabo la asignación. Para forzar una asignación de este tipo, debemos
convertir explícitamente la constante de punto flotante en un flotante:
float y = (float) 3.5; // This will compile, thanks to the cast.
o, alternativamente, podemos forzar la constante en el lado derecho de la
instrucción de asignación para que el compilador Java la vea como un flotante
usando el sufijo F, como se muestra aquí:
float y = 3.5F; // OK, because we're indicating that the constant is to be
// treated as a float, not as a double.

Otra opción más es simplemente declarar variables dobles en lugar de flotantes


siempre que deseemos representar valores numéricos de coma flotante en un
programa.

Normalmente usaremos dobles en lugar de flotadores cuando


necesitemos declarar variables de coma flotante en nuestra aplicación
SRS en la Parte 3 del libro, solo para evitar estos inconvenientes de la
conversión de tipo.

Las expresiones de tipo char se pueden convertir a cualquier otro tipo


numérico, como se ilustra en el siguiente ejemplo:
char c = 'a';
// Assigning a char value to a numeric variable transfers its
// ASCII numeric equivalent value.
int x = c;
float y = c;
double z = c;
System.out.println(x);
System.out.println(y);
System.out.println(z);
Aquí está el resultado:
97
97.0
97.0

El único tipo de Java que no se puede convertir, ya sea implícita o


explícitamente, en otro tipo es el tipo booleano.

Verá otras aplicaciones de fundición, que involucran objetos, más adelante en


el libro.

S-ar putea să vă placă și