Métodos, clases, y objetos H. Tejeda Febrero 2016 Índice 1. Llamada a métodos y colocación 2 2. Diseño de un método 3 3. Parámetros en los métodos 4 4. Métodos que devuelven un valor 6 5. Clases y objetos 7 6. Creación de una clase 8 7. Métodos de instancia 9 8. Declaración de objetos y uso de sus métodos 11 9. Constructores 13 10.Clases como tipos de datos 14 1 1. Llamada a métodos y colocación Un método es un módulo de programa que contiene una serie de sentencias que realizan una tarea. Se han visto clases de Java que contienen el método main, el cual se ejecuta automáticamente cuando se corre el programa. El método main puede ejecutar métodos adicionales, y estos métodos pueden ejecutar otros. Cualquier clase puede contener una cantidad ilimitada de métodos, y cada método puede ser llamado una cantidad ilimitada de veces. Para ejecutar un método, se debe invocar o llamar. El método llamado a su vez puede llamar a otros métodos. El método llamado se conoce también como método cliente. Hay dos ventajas principales al crear métodos. Por una parte el método main() permanece pequeño y por otra parte permite reusar los métodos. Un método es colocado dentro de una clase, pero este deberá estar afuera de cualquier otro método. No es válido anidar métodos. La colocación de los métodos en una clase no influye en el orden en el cual los métodos son llamados o ejecutados. El método main() es siempre ejecutado primero en cualquier aplicación de Java, y este podrá llamar cualquier otro método en forma arbitraria y cualquier cantidad de veces. La ejecución de otros métodos no se ejecutan sólo por estar en la clase, estos deberán ser llamados. Una clase podrı́a contener métodos que nunca sean llamados para alguna aplicación particular. En el código 1 se muestra la clase Primero con los métodos main() y nombreYDireccion(). Cuando se ejecuta el programa, el método main() primero llama al método nombreYDireccion(), el cual muestra tres lı́neas en la salida. Cuando se termina la ejecución de nombreYDireccion se devuelve la ejecución a main() para mostrar un mensaje más. 1 2 3 4 5 6 7 8 9 10 11 public c l a s s Primero { public s t a t i c void main ( S t r i n g [ ] a r g s ) { nombreYDireccion ( ) ; System . out . p r i n t l n ( ” Primera a p l i c a c i ón de Java ” ) ; } public s t a t i c void nombreYDireccion ( ) { System . out . p r i n t l n ( ” U n i v e r s i d a d Michoacana ” ) ; System . out . p r i n t l n ( ” S a n t i g o Tapia # 403 ” ) ; System . out . p r i n t l n ( ” Morelia , Mich . , 58000 ” ) ; } } Código 1: clase Primero donde main() llama a nombreYDireccion 2 2. Diseño de un método Cada método deberá incluir las siguientes dos partes: Una cabecera del método da información de como otros métodos pueden usarlo. También se le conoce como declaración. Un cuerpo del método entre un par de llaves, que contiene las sentencias que realizan el trabajo del método. También se le llama implementación. El cuerpo podrı́a no contener sentencias, pero no tiene sentido. La cabecera del método contiene lo siguiente: Especificadores de acceso opcionales. Tipo devuelto Identificador. Paréntesis. 2.1. Especificadores de acceso Los especificadores de acceso para un método Java puede ser cualquiera de los siguientes: public, private, protected, y package. Si no se indica el especificador entonces package es por defecto. Cuando se usa public significa que cualquier otra clase puede usarlo. Estos modificadores se aplican sólo para el uso de los métodos por otras clases. Para que un método pueda ser usado sin instanciar un objeto se requiere el modificador static. Los métodos que no llevan este modificador son los métodos no estáticos o de instancia. 2.2. Tipo devuelto El tipo devuelto describe el tipo de dato que el método manda de regreso al método que lo llamó. No todos los métodos regresan un valor a los métodos invocadores; para indicar que no regresa nada se debe poner como tipo devuelto void 2.3. Nombre del método El método de un nombre puede ser cualquier identificador legal, es decir, una palabra sin espacios y que no sea una palabra reservada. El método que se ejecuta primero cuando se ejecuta una aplicación deberá ser llamado main. 3 2.4. Paréntesis Estos se ponen después del identificador y podrı́an contener datos que son enviados al método. En caso de que no tengan datos, los paréntesis están vacı́os. El identificador calificado completamente es el nombre incluyendo la clase. Cuando se usa un método dentro de la misma clase, no se ocupa calificarlo completamente (aunque se puede hacer), es suficiente con el nombre del método. Pero si se quiere usar el método en otra clase se requiere calificarlo completamente. Como cuando se llama al método JOptionPane.showMessageDialog(). Dos clases diferentes pueden tener los nombres de los métodos repetidos. Dos clases en una aplicación no pueden tener el mismo nombre. 3. Parámetros en los métodos Algunos métodos requieren que se les envı́en datos cuando sean llamados. Los elementos de datos que se usan en una llamada a un método se conocen como argumentos. Cuando el método recibe los elementos de datos, estos son llamados parámetros. Los métodos que reciben datos son flexibles porque puede dar resultados distintos dependiendo de los datos que recibieron. Si en un programa se diseña un método para elevar al cuadrado valores numéricos, tiene sentido diseñar el método cuadrado para que el usuario proporcione un argumento que represente el valor que quiere elevar al cuadrado, en vez de tener que escribir métodos particulares que sólo lo hagan para un valor particular. Algo similar ocurre cuando se usa el método println() que puede recibir cualquier argumento, si este método no pudiera aceptar argumentos, no serı́a práctico usarlo. Un principio de la programación orientada al objeto es el ocultamiento de la implementación, la cual describe la encapsulación de los detalles del método. Los métodos bien escritos usan este principio, ası́ un método que llama a otro sólo debe saber el nombre y el tipo de información a mandar. Sin embargo, el cliente no necesita conocer como el método trabaja internamente (para manejar un automóvil no es necesario saber como funciona su mecanismo interno), es decir, sólo necesita entender la interfaz para llamar al método. La interfaz es la única parte del método que el cliente ve o con la que interactúa. 3.1. Métodos con un solo parámetro Para escribir la declaración de un método que pueda recibir un parámetro, se ponen los especificadores de acceso opcional, el tipo que regresa el método y el nombre del método. Además se debe incluir lo siguiente dentro de los paréntesis de la declaración: 4 El tipo de parámetro. Puede ser cualquier tipo de dato, incluyendo los tipos primitivos (int, double, etc.) o tipos de clase (String o PrintStream). Un nombre local para el parámetro. Por ejemplo, la declaración para un método público llamado prediceIncremento() que acepte el salario anual de una persona y calcule un incremento del 10 por ciento se escribe como sigue: public static void prediceIncremento(double salario) En la cabecera del método prediceIncremento(), el parámetro double salario dentro de los paréntesis indica que el método recibirá un valor de tipo double. El código 2 muestra el método completo. 1 2 3 4 5 6 7 public s t a t i c void p r e d i c e I n c r e m e n t o ( double s a l a r i o ) { double nuevaCantidad ; f i n a l double INCREMENTO = 1 . 1 0 ; nuevaCantidad = s a l a r i o ∗ INCREMENTO; System . out . p r i n t l n ( ” El nuevo s a l a r i o con i n c r e m e n t o e s ” + nuevaCantidad ) ; } Código 2: Método prediceIncremento() El método prediceIncremento() es un método void porque no necesita regresar algún valor al método que lo llamó. En la implementación se recibe el valor salario, se multiplica por la constante INCREMENTO, y se muestra el resultado. El argumento para la llamada al método prediceIncremento() puede ser un valor constante, una variable, o una expresión. El parámetro salario es un variable local para el método prediceIncremento(). La variable y la constante declaradas dentro del método son también locales. 3.2. Método con múltiples parámetros Un método puede requerir más de un parámetro. Se pueden pasar múltiples argumentos a un método listando los argumentos en la llamada al método y separándolos con comas. Por ejemplo, en vez de tener un método prediceIncremento() que agrega un 10 por ciento de incremento, se podrı́a preferir crear un método al cual se le puedan pasar dos valores, el salario que será incrementado y el porcentaje en que se incrementa. El código 3 muestra un método que usa los dos parámetros. 5 public s t a t i c void prediceIncrementoConTasa ( double s a l a r i o , double t a s a ) { double nuevaCantidad ; nuevaCantidad = s a l a r i o ∗ ( 1 + t a s a ) ; System . out . p r i n t l n ( ” El nuevo s a l a r i o con i n c r e m e n t o e s ” + nuevaCantidad ) ; } 1 2 3 4 5 6 7 Código 3: Método prediceIncrementoConTasa() Los dos parámetros están dentro de los paréntesis en la cabecera del método. Una coma separa cada parámetro, y cada parámetro requiere su propia declaración de tipo ası́ como su propio identificador. Se puede escribir un método que tome cualquier cantidad de parámetros en cualquier orden. Cuando se llame al método, los argumentos enviados al métodos deben empatar en orden (cantidad y tipo) los parámetros listados en la declaración del método. La firma del método es la combinación del nombre del método y la cantidad, tipos, y orden de los argumentos. 4. Métodos que devuelven un valor Un método termina cuando cualquiera de los siguientes eventos sucede: El método ejecuta todas sus sentencias. El método lanza una excepción. Las excepciones son errores El método alcanza la sentencia return. Esta sentencia hace que un método termine y que el flujo del programa regrese al método invocador. La sentencia return también puede regresar un valor al método invocador. El tipo devuelto por un método puede ser cualquier tipo usado en Java (tipos primitivos o tipos de clase). Un método también puede regresar nada, en el cual el tipo devuelto es void. El tipo regresado por el método también se conoce como tipo del método. El método prediceIncremento() del código 2 no devuelve algún valor, ası́ su tipo devuelto es void. Si se quiere que el método regrese el nuevo salario calculado en vez de mostrarlo, en el código 4 se muestra como debe hacerse. 6 public s t a t i c double p r e d i c e I n c r e m e n t o ( double s a l a r i o ) { double nuevaCantidad ; f i n a l double INCREMENTO = 1 . 1 0 ; nuevaCantidad = s a l a r i o ∗ INCREMENTO; return nuevaCantidad ; } 1 2 3 4 5 6 Código 4: Método prediceIncremento() devolviendo un double El tipo devuelto en la declaración del método debe empatar con el tipo del valor usado en la sentencia return; si no se hace, la clase no se puede compilar. Un método puede regresar, a lo más, un valor. Nota. Todos los métodos excepto los void requiren una sentencia return que regrese un valor del tipo apropiado. No se deben poner sentencias después de la sentencia return de un método. Tales sentencias son inalcanzables porque el flujo lógico deja el método en la sentencia return. Las sentencias inalcanzables son también llamadas código muerto. Si un método regresa un valor este se usa normalmente, sin embargo no se está obligado. Cuando se llame al método prediceIncremento, se podrı́a querer asignar el valor regresado a una variable tipo double, como en la siguiente sentencia: miNuevoSalario = prediceIncremento(miSalario); Se puede también usar el valor devuelto por el método directamente, sin guardarlo en alguna variable. Por ejemplo se puede mostrar el valor regresado en la siguiente sentencia: System.out.println("El nuevo salario es " + prediceIncremento(miSalario)); También se puede realizar aritmética con el valor devuelto por el método, como en la siguiente sentencia. double liquido = prediceIncremento(miSalario) - gastos; 5. Clases y objetos En la programación orientada al objeto, todo es un objeto, y cada objeto es un miembro de una clase. Un programador podrı́a decir que su perro chihuahua es una instancia de la clase 7 Perro. Esta sentencia representa una relación es-una. Un objeto es una instanciación de una clase, o un ejemplo tangible de una clase. El concepto de clase es útil por su reusabilidad. Los objetos obtienen sus atributos de sus clases, y todos los objetos tienen atributos predecibles porque son miembros de ciertas clases. También los objetos tienen métodos asociados con ellos, y cada objeto que es una instancia de una clase posee los mismos métodos que otras instancias. En la programación orientada al objeto se crean clases de las que se puede instanciar objetos, y otras veces se crean clases para ejecutar aplicaciones. Las clases aplicación instancian objetos que usan los objetos de otras clases. En ocasiones se escriben clases para ambos casos. Se puede llamar a una aplicación, o clase, que instancie objetos de otra clase preescrita clase cliente o clase de usuario. 6. Creación de una clase Para crear una clase, se debe asignar un nombre a la clase, y se deberá determinar que datos y métodos serán parte de la clase. Suponer que se decide crear una clase llamada Empleado. Una variable de instancia de Empleado podrı́a ser el número de empleado, y dos métodos necesarios podrı́an ser el método para set (poner o dar un valor) al número de empleado y otro método para get (obtener o recuperar) el número del empleado. La cabecera de una clase tiene tres partes: Especificador de acceso opcional. Palabra reservada class. Un identificador legal para nombrar la clase. Una cabecera para una clase que representa un empleado podrı́a ser: public class Empleado El modificador de clase public permite el mayor acceso posible. Las clases que son public son accesibles para todos los objetos, además pueden ser extendidas (base para otras clases). De la clase Empleado se podrı́an derivar las clases EmpleadoAsalariado y EmpleadoPorHoras sin requerir iniciar desde cero. Estas nuevas clases podrı́an convertirse en una extensión de la original, heredando sus datos y sus métodos. Después de escribir la cabecera de la clase se escribe el cuerpo de la clase encerrado entre un juego de llaves. El cuerpo contiene los datos y los métodos para la clase. Los componentes de datos de una clase son referidos como campos de datos para distinguirlos de otros componentes. Enseguida se muestra la construcción de la clase Empleado conteniendo el 8 campo de datos numEmp. Los campos de datos son variables que se declaran dentro de una clase pero fuera de cualquier método. public class Empleado { private int numEmp; } En el código previo, el campo de dato numEmp no está precedido por la palabra reservada static. Si hubiese sido insertada, sólo un valor numEmp serı́a compartido por todos los objetos Empleado que sean instanciados. Como numEmp no está precedida por static, cuando se creen, o instancien, objetos de la clase, cada objeto tendrá su propio numEmp. Un campo no estático, como numEmp, es una variable de instancia de la clase. Asignar acceso privado a un campo significa que ninguna otra clase puede acceder los valores del campo, y sólo a los métodos de la misma clase se permite operar. El principio usado en tener acceso privado es llamado en ocasiones ocultamiento de información. El resultado del arreglo de datos privados y métodos públicos da una forma de controlar hacia afuera el acceso de los datos. Los campos de datos de la clase son frecuentemente privados y no estáticos. Los campos de datos que llevan public son los campos con static y final, es decir, cuando una clase contiene un valor que no cambia y no se requiere crear un objeto. Por ejemplo, la clase Math contiene un campo public llamado PI que puede usarse sin instanciar un objeto Math. 7. Métodos de instancia Además de los datos, las clases contienen métodos. Los métodos que ponen o cambian valores de campos son llamados métodos mutadores; métodos que recuperan valores son llamados métodos accesores. En Java, se tiene la convención de que los métodos mutadores inicien con set, y los accesores inicien con get. El siguiente código muestra el método accesor y el mutador para el campo numEmp para la clase Empleado. public void setNumEmp(int emp) { numEmp = emp; } public int getNumEmp() { return numEmp; } Observar que en los métodos anteriores no se usa el modificador static. Los métodos no estáticos, también llamados métodos de instancia, son usados con instanciaciones de objetos. Dentro de un objeto se pueden usar métodos static o no estáticos, pero sólo los 9 métodos no estáticos se comportan de forma única para cada objeto. No se puede usar un método no estático sin un objeto. 7.1. Organización de clases La mayorı́a de las clases tienen varios campos de datos y métodos. Para la clase Empleado, además del número del empleado, se necesita guardar apellidos, nombres y salario. El siguiente código muestra como se podrı́an codificar los campos de datos para la nueva clase Empleado2. public class Empleado2 { private int numEmp; private String apellidoEmp; private String nombreEmp; private double salarioEmp; // los métodos irán aquı́ } Como hay dos componentes String en el código anterior, se podrı́a declarar con la siguiente sentencia: private String apellidoEmp, nombreEmp; Se pueden poner los campos de datos y métodos en cualquier orden dentro de una clase. Se sigue la convención de colocar todos los campos de datos primero para ver los nombres y los tipos de datos antes de leer los métodos que los usan. Para facilitar la localización de los métodos, muchos programadores los ponen en orden alfabético. Otros programadores ponen los métodos en pareja. En el código 5 se muestra la definición completa de la clase Empleado2. 10 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public c l a s s Empleado2 { private int numEmp ; private S t r i n g apellidoEmp ; private S t r i n g nombreEmp ; private double salarioEmp ; public int getNumEmp ( ) { return numEmp ; } public void setNumEmp ( int empl ) { numEmp = empl ; } public S t r i n g getApellidoEmp ( ) { return apellidoEmp ; } public void setApellidoEmp ( S t r i n g nombre ) { apellidoEmp = nombre ; } public S t r i n g getNombreEmp ( ) { return nombreEmp ; } public void setNombreEmp ( S t r i n g nombre ) { nombreEmp = nombre ; } public double g et Sa l ar io E mp ( ) { return salarioEmp ; } public void s e t S a l a r i o E m p ( double s a l ) { salarioEmp = s a l ; } } Código 5: La clase Empleado2 con varios campos de datos y sus métodos correspondientes. 8. Declaración de objetos y uso de sus métodos Declarar una clase no crea ningún objeto. Una clase es una descripción abstracta de lo que el objeto será. Un proceso de dos pasos crea un objeto que es una instancia de una clase. Primero, se proporciona un tipo y un identificador (como cuando se declara cualquier variable) y después se asigna memoria para ese objeto. Para declarar un Empleado se hace como sigue: Empleado2 unEmpleado; Cuando se declara la instancia unEmpleado de la clase Empleado2, se le está notificando al compilador que se usará el identificador unEmpleado. Para asignar la memoria necesitada 11 para un objeto, se debe usar el operador new. Las dos sentencias que apartan la memoria suficiente para tener un Empleado2 son: Empleado2 unEmpleado; unEmpleado = new Empleado2(); En vez de usar dos sentencias, se puede declarar y reservar memoria para unEmpleado en una sentencia, como se muestra enseguida: Empleado2 unEmpleado = new Empleado2(); En esta sentencia, unEmpleado se convierte en una referencia al objeto—el nombre para una dirección de memoria donde el objeto esta guardado. El signo de igual es el operador de asignación, ası́ un valor es asignado a unEmpleado en la declaración. El operador new está asignando una porción de la memoria no usada para unEmpleado. El valor que la sentencia le asigna a unEmpleado es una dirección de memoria en la cual el objeto se encuentra. En la parte final de la sentencia después del operador new, Empleado2(), con sus paréntesis, es el nombre de un método especial, llamado constructor, que construye un objeto Empleado2. Se puede escribir el propio constructor para una clase, pero cuando no se escribe el constructor, Java proporciona uno. El constructor escrito por el programador, o el dado por Java, tienen el mismo nombre que clase de la cual los objetos son construidos. Después de que un objeto ha sido instanciado, sus métodos pueden ser accedidos usando el identificador del objeto, un punto, y una llamada al método. En el código 6 de la clase DeclaraDosEmpleados se muestra una aplicación que instancia dos objetos Empleado2. Los dos objetos usan los métodos setNumEmp() y getNumEmp() porque son públicos, y deben usarse con un objeto Empleado2 porque los métodos no son estáticos. 1 2 3 4 5 6 7 8 9 10 11 public c l a s s DeclaraDosEmpleados { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Empleado2 c h o f e r = new Empleado2 ( ) ; Empleado2 t e c n i c o = new Empleado2 ( ) ; c h o f e r . setNumEmp ( 1 2 3 ) ; t e c n i c o . setNumEmp ( 4 5 6 ) ; System . out . p r i n t l n ( ” El número d e l c h o f e r e s ” + c h o f e r . getNumEmp ( ) + ” y e l número d e l té c n i c o e s ” + t e c n i c o . getNumEmp ( ) ) ; } } Código 6: Clase DeclaraDosEmpleados. 12 9. Constructores Cuando se crea una clase, como Empleado, y se instancia un objeto con una sentencia como la siguiente, se está llamando al constructor de la clase Empleado que es proporcionado por defecto por el compilador de Java. Empleado mozo = new Empleado(); Un constructor establece un objeto; un constructor por defecto es uno que no requiere argumentos. Un constructor por defecto es creado automáticamente por el compilador Java para cualquier clase creada en la que no se haya escrito un constructor. Cuando se crea un objeto Java inicializa los campos de datos de un objeto de la siguiente forma: Campos numéricos son puestos a cero. Campos char son puestos al codigo Unicode ’\u0000’ Campos booleanos son puestos a false. Campos que son referencias de objeto (como campos String) son puestos a null (vacı́o). Si no se quiere esta inicialización en los campos de un objeto, o se quieren realizar tareas adicionales, se puede escribir un contructor. Cualquier constructor deberá tener el mismo nombre que la clase que declara, y los constructores no pueden tener un tipo devuelto. Los constructores se declaran públicos para que otras clases puedan instanciar objetos que pertenecen a esa clase. Cuando se escribe un constructor para una clase, ya no se tiene acceso al proporcionado automáticamente por Java. Si se quiere que cada objeto Empleado tenga por defecto un salario inicial de $2000.00 por semana, se podrı́a escribir el constructor para la clase Empleado como se muestra enseguida: public Empleado() { salarioEmp = 2000.00; } Se puede escribir cualquier sentencia en un constructor. El constructor se coloca en cualquier lugar dentro de la clase y fuera de cualquier otro método. Frecuentemente, los programadores ponen primero el constructor porque es el primer método usado cuando un objeto es creado. No se obliga a escribir un constructor para un clase; Java da un constructor por defecto si la clase no contiene un constructor explı́cito. 13 10. Clases como tipos de datos Las clases que son creadas son tipos de datos. En ocasiones se refieren a las clases como Abstract Data Type (tipo de dato abstracto), o ADT. Un tipo de dato abstracto es un tipo cuya implementación está oculta y se accede a través de sus métodos públicos. Una clase también puede ser llamada como tipo de dato definido por el programador, es decir, es un tipo que no está construido en Java. Una clase es un tipo compuesto, la clase está compuesta por partes más pequeñas. Los tipos primitivos no son tipos compuestos. Los tipos primitivos pueden también ser llamados tipos escalares. Cuando se declara un objeto tipo primitivo, se proporciona su tipo e identificador. Cuando se declara un objeto de alguna clase, se hace lo mismo. Una vez que existen se pueden usar en formas muy similares. Por ejemplo, suponer que se declara un int llamado miEntero y un Empleado llamado miEmpleado. Entonces cada uno puede ser pasado a un método, regresado desde un método, o asignado a otro objeto del mismo tipo de dato. En el código 7 de la clase MetodosQueUsanEmpleado2 se muestra una aplicación donde el método main() usa dos métodos. El método obtenerDatosEmpleado() regresa un objeto Empleado y el método mostrarEmpleado() acepta un objeto Empleado como parámetro. El objeto Empleado es pasado y devuelto como cualquier tipo primitivo. 14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import j a v a . u t i l . Scanner ; c l a s s MetodosQueUsanEmpleado2 { public s t a t i c void main ( S t r i n g a r g s [ ] ) { Empleado2 miEmpleado ; miEmpleado = obtenerDatosEmpleado ( ) ; mostrarEmpleado ( miEmpleado ) ; } public s t a t i c Empleado2 obtenerDatosEmpleado ( ) { Empleado2 emplTemp = new Empleado2 ( ) ; int i d ; double s a l ; Scanner e n t r a d a = new Scanner ( System . i n ) ; System . out . p r i n t ( ” I n g r e s a r ID d e l empleado ” ) ; id = entrada . nextInt ( ) ; emplTemp . setNumEmp ( i d ) ; System . out . p r i n t ( ” I n g r e s a r e l s a l a r i o d e l empleado ” ) ; s a l = e n t r a d a . nextDouble ( ) ; emplTemp . s e t S a l a r i o E m p ( s a l ) ; return emplTemp ; } public s t a t i c void mostrarEmpleado ( Empleado2 empl ) { System . out . p r i n t l n ( ”\nEmpleado #”+empl . getNumEmp ( ) + ” S a l a r i o $ ” + empl . g et S al ar i oE mp ( ) ) ; } } Código 7: Aplicación MetodosQueUsanEmpleado2. 15