5 – Estructuras de selección, repetición, salto, continúe, Creación de objetos (Instancias), Paquetes y Visibilidad. Programación Orientada a Objetos Proyecto Curricular de Ingeniería de Sistemas Descripción de las estructuras de programación, de tipo bifurcación o toma de decisiones, en Java. Se tratan de las mismas estructuras que pueden encontrarse en cualquier otro lenguaje, si sabes ya programar ten en cuenta que lo único que necesitas aprender es la sintaxis y eso se consigue mucho mejor programando así que puedes pasar por alto este punto. Bifurcaciones: Permiten ejecutar código en función de una expresión evaluada Bifurcaciones if: Tienen las siguientes posibilidades en su sintaxis: if (ExpresionBooleana){conjuntoDeSentencias} if (ExpresionBooleana) {conjuntoDeSentencias} else {conjuntoAlternativo} if (ExpresionBooleana) {conjuntoDeSentencias} else if {conjuntoAlternativo} else if {conjuntoAlternativo2} Ejemplos: public class prueba { public static void main (String args[]){ int i=5; if (i == 5){ System.out.println(" i vale 5 ");} else {System.out.println("i no vale 5");} i=4; if (i == 5){ System.out.println(" i vale 5 ");} else if (i < 5){System.out.println("i es menor que 5");} else if (i > 5){System.out.println("i es mayor que 5");} } } Bifurcaciones switch Son las que permiten realizar varias acciones distintas dependiendo del estado de una variable. Switch (Expresion){ Case valor1: conjuntoDeSentencias; break; Case valor2: SentenciasAlternativas; break; Case valor3: SentenciasAlternativas2; break; Case valor4: SentenciasAlternativas3; break; } La sentencia 'break' detrás de cada opción de case sirve para que no evalue el resto de opciones sino que se salga directamente del 'Switch', asi que dependiendo de lo que quieras hacer la pondrás o no, recuerde que break rompe y la sentencia continué se emplea cuando ninguna de los case se hace. Ejemplos: public class prueba { public static void main (String args[]){ char i ='2'; switch (i) { case '1': System.out.println( "i contiene un 1"); break; case '2': System.out.println( "i contiene un 2"); break; case '3': System.out.println( "i contiene un 3"); break; default : System.out.println( "i no es 1, 2 ó 3 "); } } } Vemos los bucles for, while y do while, junto con otras estructuras de programación como break, continue y return. Los bucles se utilizan para ejecutar un conjunto de instrucciones varias veces basándose siempre en una condición que decidirá si se sigue repitiendo o no. Veamos los tipos que hay. Bucle While while (expresion) {sentencias} Las instrucciones dentro de las llaves se ejecutan mientras la expresión sea verdadera. public class prueba { public static void main (String args[]){ int i=5; while ( i > 0 ) {i --;} // las llaves aquí se podían haber omitido, puesto // que solo hay una sentencia. System.out.println("Ahora i vale 0"); } } Bucle For Es un bucle más "fijo", permite ejecutar el conjunto de sentencias un numero determinado de veces fijado al principio del bucle y funciona por tanto como un contador. Su expresión general seria como la que sigue: for (inicialización, expresionBooleana, incremento) {conjuntoDeSentencias;} public class prueba { public static void main (String args[]){ for (int i= 0; i <10; i++){ System.out.println("el valor de i es: " + i); } } } Este ejemplo nos mostraría por la pantalla diez líneas diciéndonos el valor creciente de 'i' de cero a nueve. Bucle do while Es igual al bucle while anteriormente visto, solo que ahora se evalúa la expresión al final del bucle, por lo que ese conjunto de sentencias se ejecuta al menos una vez: public class prueba { public static void main (String args[]){ int i=5; do {i --;} // las llaves aquí se pueden omitir puesto while ( i > 0 ); // que solo hay una sentencia. } } Este ejemplo similar al anterior para el bucle while se diferencia en que ejecuta una vez mas las sentencias en su cuerpo puesto que comprueba la condición posteriormente. Sentencias Break, Continue y Return Antes hemos hablado de la sentencia Break con las bifurcaciones switch. Pues bien, esta sentencia tiene un valor mas amplio. La sentencia break nos permite salirnos del bloque de sentencias (encerrado entre llaves) o el bucle que estamos ejecutando, sin ejecutar las sentencias que resten para el final o las restantes iteraciones del bucle. Por ejemplo: public class prueba { public static void main (String args[]){ int i=5; do{ i --; if (i == 3) break; } while ( i > 0 ); // En este ejemplo cuando i tenga el valor 3 // se abandonará el bucle. } } La sentencia Continue solo es válida para bucles, sirve para no ejecutar las sentencias que restan para la finalización de una iteración de ese bucle, continuando después con las siguientes iteraciones del bucle. Por ejemplo: public class prueba { public static void main (String args[]){ int i=5; do{ if (i == 3) continue; i --; }while ( i > 0 ); // En este ejemplo cuando i tenga el valor 3 // se abandonará la iteración y por tanto el // bucle no tendrá fin puesto que no se // ejecutaría la sentencia de decremento. } } Tanto la sentencia continue como break se pueden utilizar con etiquetas para poder discriminar los bucles que quieren afectar en caso de que se encuentren en un bucle anidado. Por ejemplo: public class prueba { public static void main (String args[]){ bucle1: for (int i=0; i<10; i++){ bucle2:{ System.out.println("i="+i+" Bucle 2"); for (int j=0; i<10; j++){ System.out.println("j="+j+" Bucle 2"); if (j==5) { System.out.println("si j=5 del Bucle 2"); break bucle2; //break bucle1; }//cierra if }//cierra for del j }//cierra bucle2 } // cuando j llega a 5 el bucle2 deja de // ejecutarse hasta la siguiente iteracion // del bloque1 } } Por último vemos la sentencia return. Esta sentencia nos permite finalizar también un conjunto de sentencias, con la peculiaridad esta vez de que también finaliza el método o función que en el que se encuentre. En el caso de que queramos devolver un valor desde esa función o método lo debemos poner a continuación de return. Por ejemplo: public static void main(String[] args) { System.out.print(funcionEjemplo()); } static int funcionEjemplo(){ int i=0; while (i < 100){ i++; return i; // Cunado encuatra un return retorna este valor y termina el metodo //este absurdo metodo nos devuelve 1 como puedes comprobar } //Nunca se llega a este punto ya que al encontrar el 1er return //el metodo termina imprimir(); //Siempre que un metodo devuelva algo, la ultima sentecia en la //declaracion debe ser el return, de lo contrario marca error. return i; } static void imprimir (){ System.out.println("esto no se imprime"); } } } Bloque Try – Catch – Finally Se trata de unas sentencias de control relacionadas con el tratamiento de excepciones, no tiene nada que ver con las estructuras de control de bucle, vistas en este capitulo, salvo porque es también una estructura de control. La comentamos a continuación, aunque se verá con ejemplos más adelante. El tratamiento de excepciones se utiliza para detectar errores cuando se ejecutan nuestros programas y tratarlos del modo que nosotros queramos. Los errores cazados por un sistema de tratamiento de excepciones no bloquean el programa y el manejo de excepciones nos permite hacer cosas cuando esos errores ocurren. Por ahora solo es bueno que sepas que existen, ya las veremos en otro momento. Seguimos explorando el lenguaje de programación Java, esta vez introduciendo el trabajo con objetos y las partes de un programa típico orientado a objetos y escrito en Java. Vamos a intentar entender como funciona un programa en Java para poder empezar a programar nosotros posteriormente. se intentara también explicar algo sobre los conceptos básicos de la Programación orientada a objetos, mientras avanzamos. Seguramente en un futuro ampliaremos los contenidos sobre programación orientada a objetos dentro de el contenido de la materia por la importancia que esta tiene. Vamos adelante. Estructura básica de un programa en Java En Java, como en cualquier otro lenguaje orientado a objetos, abandonamos el modo de entender un programa que utilizábamos anteriormente para aproximarnos a un modo más cercano a "la vida misma". Los programas ahora estarán divididos en clases. Una clase en si se puede entender como un programa independiente, tiene sus propios datos y también maneja esos datos "a su modo". La relación con la vida misma la podemos ver en el siguiente comentario: Imaginemos que dos clases tal y como las hemos explicado anteriormente son "la clase mecánico" y la "clase panadero". Cada una de estas clases tiene sus propias herramientas y sus propias tareas, por ejemplo, el panadero tiene "harina" y una de sus tareas es "amasar", mientras que el mecánico tiene "bujías" y una de sus tareas es "limpiar bujías". Lo importante de todo esto es que cada uno hace muy bien su tarea pero no tiene sentido "llevar el coche a la panadería de la esquina" ni "pedir una Alineado de $500 junto con un cambio de aceite". Vamos a estudiar unas pequeñas clases que nos servirán en un futuro para implementar un ejercicio un poco más complicado. Estas clases son la clase "ficha" y la clase "tablero" que nos servirán para implementar con el tiempo un juego de las "cuatro en raya". public class Fichas { String color; public Fichas(String c){color=c;} public String dameColor(){return(color);} } Esta va a ser la clase ficha. Veámosla un poco: Esta clase tiene una variable "color" que es un String. El tipo String no es un tipo primitivo en Java, es una clase que está dentro del API de Java, mas concretamente dentro del paquete "Java.lang" y que además siempre se incluye por defecto en cada programa Java que hagamos. Por tanto, lo que estamos haciendo en la segunda línea de nuestro programa es declarar un objeto sin valor de la clase String. La tercera y la cuarta línea son dos métodos de la clase "Fichas" que estamos definiendo. El primer método es un constructor. Un constructor es un método que se llama con la sentencia "new", es decir cuando alguien quiera crear un objeto y que nos define bajo que condiciones se crea ese objeto, ya lo entenderás mejor. En este caso para que alguien quiera crear una ficha tiene que pasar un objeto "String" como parámetro y obtendrá a cambio un objeto de la clase "Fichas" del color que ha solicitado. El segundo método nos devuelve un objeto "String" con el valor del color que tiene el objeto ficha en ese momento. public class Tablero { Fichas estadoTablero[][]; public Tablero(){estadoTablero=new Fichas [6][7];}; public boolean verSiLlena(int índice){ return(estadoTablero[7][indice]==null);}; } Bueno, esta segunda clase que estudiamos tiene también un objeto interno llamado "estadoTablero" que en este caso es un "arreglo" cuyas posiciones son de la clase anteriormente declarada "Fichas". También tenemos un "constructor" para el objeto "estadoTablero" que crea ese "arreglo" con las dimensiones que queremos. Y en este caso hay una función que nos dice si la columna por la que nos interesamos con el parámetro de entrada "índice" está llena. De estos dos ejemplos de clases aún nos quedan algunos conceptos por aprender, como por ejemplo eso del "constructor". Lo veremos con detenimiento más adelante. Vemos lo que son las clases, cómo crearlas y algunos detalles adicionales de su uso. En si, y como se ha comentado anteriormente en las guías anteriores, las clases marcan la estructura básica de un programa tanto en Java como en la programación orientada a objetos en general. Una clase es el producto de enfocar la programación a los datos más que a las funciones. Por tanto una clase es una colección de datos y además para operar con ellos una serie de funciones propias de la clase. Veamos por ejemplo la clase "Fichas" definida anteriormente, su único dato es "el color" y la única operación que permite es saber el color de la ficha en cualquier momento. Eso permite un acceso restrictivo a los datos según la función de los mismos. En este caso la clase es así basándose en la vida misma: No creo que nadie haya cambiado el color de una ficha jugando a las "cuatro en raya" y en caso positivo no tendría muy buenas intenciones al hacerlo. Además de este método básico de protección de los datos, Java permite algunos más que vemos ahora mismo. Cuando declaramos una clase lo primero que ponemos es la cabecera: public class Fichas { (cuerpo de la clase) } La primera palabra nos proporciona la posibilidad de dar permisos de accesos a nuestra clase, los permisos son los siguientes: • • "Public": Una clase "public" es accesible desde cualquier otra clase, no obstante para que esto suceda debe ser primero accesible el "package" de esa clase "public". Para que un "package" sea accesible debe de estar en el directorio que señala la variable "CLASSPATH" que definimos al instalar nuestro entorno Java y, claro está, tener permiso de lectura en ese directorio. "Package": Usar este identificador en la cabecera de la clase es opcional, pues es la opción por defecto en java, es decir, si escribimos: class Fichas {(Cuerpo de la clase)} Es lo mismo que si escribimos: package class Fichas {(Cuerpo de la clase)} Las clases "package" ( que podemos entender como las que no son "public" ) son accesibles solo desde su propio package. Esto es relativo a la accesibilidad de las clases. Más conceptos relativos a la accesibilidad hacen referencia a los atributos internos de una clase que ya comentamos en sesión anterior, y a sus métodos. En cuanto al nombre de la clase consideraremos varias cosas. Debe de obedecer al convenio de nombres de Java y coincidir con el nombre del fichero ".java" en el que se guardará la clase. Lo normal es que cada clase vaya incluida en un único fichero pero claro está, nos puede interesar por algún motivo meter varias clases en un único fichero. En este caso solo puede haber una clase public que es la que dará el nombre a dicho fichero. En caso de que no hubiese una clase public el compilador entenderá que la clase "principal" de ese fichero es la que concuerda con el nombre del mismo, por lo que evidentemente dos clases con un mismo nombre no son permitidas en un mismo fichero. Por último para explicar la estructura de una clase se explicaran los elementos habituales en la definición de su cuerpo. Primero se suelen declarar, al menos, las variables internas de esa clase y posteriormente se definen los constructores y los métodos que dispondrá la clase. Lo entenderemos mejor más adelante En la definición de constructores y métodos tenemos que tener en cuenta un nuevo concepto de la programación orientada a objetos. La sobrecarga. La sobrecarga consiste en poder tener varios métodos o constructores con el mismo nombre dentro de una misma clase y que no hagan las mismas cosas. Esto se consigue de una manera muy sencilla, se diferencian entre ellos mediante el número y tipo de parámetros que reciben. Veamos un ejemplo: /*tenemos dos métodos que pueden por ejemplo obtener el área de una figura geométrica en concreto, podrían ser:*/ float obtenerAreaCirculo(Circulo ci){ /* ... */ } float obtenerAreaCuadrado(Cuadrado cu){ /* ... */ } /*en Java esto se puede abreviar teniendo dos métodos sobrecargados, por ejemplo: */ float obtenerArea(Circulo ci){ /* ... */ } float obtenerArea(Cuadrado cu){ /* ... */ } /*A la hora de ejecutar el método obtenerArea se utilizará el que corresponda al parámetro que se le pase por cabecera*/ Java permite organizar las clases mediante agrupaciones o packages. Efectivamente vamos a necesitar organizar nuestras clases en algún momento porque nuestro desarrollo puede ir creciendo, y tener un cúmulo de clases todas juntas en un directorio, sin ningún tipo de organización física ni lógica, no nos ayudará demasiado. Java nos permite con los "Packages" evitar esta situación de un modo bastante elegante y ordenado. Un "Package" es básicamente una agrupación de clases, vemos por ejemplo que la versión 1.2 de Java incluye un total de 59 "Packages" para organizar todo su API, la versión del JDK 1.5 posee 165 packages. Cualquier grupo de clases se puede organizar dentro de un "package" pero evidentemente lo más normal es que tus clases las organices por algún motivo. Las clases se suelen organizar según la relación entre ellas. Por ejemplo si tuviésemos un grupo de clases que permiten hacer una ordenación de elementos en un arreglo podríamos organizarlas de este modo: Quicksort.class; // Clase para ordenar arreglos con el método quicksort Burbuja.class; // Clase para ordenar arreglos con el método de la burbuja Seleccion.class; // Clase para ordenar arreglos con el método de selección Insercion.class; // Clase para ordenar arreglos con el método de inserción directa /* Podemos englobar estas clases en un package puesto que todas están relacionadas en su cometido. Crearemos por tanto el "package ordenaciones" para que podamos acceder a ellas de este modo: */ ordenacion.Quicksort.class; // Ejemplo acceso a quicksort en el package ordenación /* Igualmente podríamos tener también clases para la búsqueda de un elemento en un arreglo en tal caso repetiríamos el proceso y por ejemplo tendríamos el "package tratamientoArreglos" de este modo: */ tratamientoArreglos.ordenacion.Quicksort.class; // Ejemplo acceso a quicksort dentro del // package ordenación que a su vez está // dentro del package tratamientoArreglos. El uso de "Packages" no solo nos permite organizar nuestras clases, sino que nos permite diferenciar clases que siendo distintas tengan que tener el mismo nombre, es decir, ayuda a java con la resolución de nombres. También como hemos visto por encima anteriormente nos permite ciertas normas para controlar el acceso a clases. Veamos ahora como utilizar los "packages" para nuestras clases. Lo primero que tenemos que tener en cuenta es que todas las clases que van a pertenecer a un mismo "package" tienen que estar en un mismo directorio que debe coincidir en nombre con el nombre del "package". Vemos que por tanto el nombre completo de una clase de un "package" equivale a su ruta desde el directorio que tomemos de base para nuestras clases. El convenio de nombres de Java establece también que los nombres de los "packages" empiecen por minúsculas. En segundo lugar para utilizar los "packages" en nuestras clases tenemos que incluir al principio del fichero de la clase (antes de cualquier otra sentencia) la linea: Package nombreDelPackage; Por último veamos la sentencia "import". "import" permite importar un "package" a nuestra clase Java. Esto nos permite acceder (en caso de que sean accesibles) sin usar todo el nombre del "package" a cualquier clase dentro de él. Veámoslo en el siguiente ejemplo: Import tratamientoArreglos.ordenación.*; //Importamos todas las clases //del "Package" de ordenación de //arreglos que teníamos organizado. /*tiramos líneas de código en Java hasta que... */ Quicksort arregloQuick= new Quicksort(); //... En cualquier momento de nuestra clase // podemos hacer uso de la clase Quicksort sin // utilizar toda la ruta del package. Hay que tener también en cuenta que importando un "package" no importamos todos sus "subpackages" sino solo todas las clases de ese "package". También podemos simplemente importar solo una clase dentro de ese "package" poniendo en vez de un asterisco el nombre de la clase a importar. Para acabar con este punto se realizara el siguiente comentario acerca del API de Java. Ya se ha dicho que las clases del API de Java están organizadas en "packages" según su función. Hay "packages" preparados para acometer las mas variadas tareas. Es ejercicio del lector informarse de que "packages" le interesan en su desarrollo y del funcionamiento de las clases incluidas en estos. Nosotros veremos ciertos "packages" de uso habitual durante el curso. Como ya se comento, en java no se empieza desde cero, tenemos un conjunto de clases que podemos incluir en nuestro programa sin necesidad de importar ningún "package". Esas clases son las que pertenecen al "package" Java.lang. este "package" incluye las clases básicas de Java como por ejemplo la clase "Arreglo" que utilizaremos muy habitualmente. Ejercicio practico para el Lector: Empleando el IDE de desarrollo eclipse y las ventajas que este ofrece en la creación de clases y paquetes cree el siguiente proyecto y pruébelo, verifique a trabes del explorador de su sistema operativo el contenido de los paquetes creados y se fijara que son carpetas. Estructura en el explorar del paquetes del proyecto a crear A continuación se anexa el codigo de las clases Globo y JugarGloba para que sean implementadas y probadas en el IDE de desarrollo Eclipse. //Paquete objeto que contiene la clase Globo package objeto; import java.awt.*; public class Globo{ private int diametro; private int xCoord, yCoord; public Globo (int diametroInicial, int xInicial, int yInicial) { diametro = diametroInicial; xCoord = xInicial; yCoord = yInicial; } public void cambiarTamaño (int cambio) { diametro = diametro + cambio; } public void mostrar (Graphics g) { g.drawOval (xCoord, yCoord, diametro, diametro); } } //Paquete principal que contiene la clase JugarGlobo package principal; import java.applet.Applet; import java.awt.event.*; import java.awt.*; import javax.swing.JFrame; import objeto.Globo; public class JugarGlobo extends Applet implements ActionListener { private Button agrandar, reducir; private Globo miGlobo; public void init() { agrandar = new Button ("Agrandar"); add (agrandar); agrandar.addActionListener(this); reducir = new Button ("Reducir"); add (reducir); reducir.addActionListener(this); miGlobo = new Globo (20, 50, 50); } public void actionPerformed(ActionEvent event) { if (event.getSource() == agrandar) miGlobo.cambiarTamaño(10); if (event.getSource() == reducir) miGlobo.cambiarTamaño(-10); repaint(); } public void paint (Graphics g) { miGlobo.mostrar(g); } public static void main (String Args[]){ JFrame f = new JFrame("Applet desde Consola"); //crear una instancia de JugarGlobo JugarGlobo start = new JugarGlobo(); //Agregar la instancia del applet al marco f.add(start); //inicializar las variables al ancho y el alto de la tag <applet> int width = 400; int height = 400; f.setSize(width, height); //llamar a init() y a start() si es necesario start.init(); //hacer visible el marco f.show(); } }