miércoles, 22 de febrero de 2012

2.6 Sobrecarga de métodos

Descarga el documento de este tema aquí

Sobrecarga de Métodos.


En Java es posible definir dos o más métodos dentro de la misma clase que comparten el mismo  nombre, siempre y cuando sus declaraciones de parámetro son diferentes. Cuando esto sucede, los métodos se dicen a sobrecargarse, y el proceso se conoce como la sobrecarga de métodos. Sobrecarga
de métodos es una de las maneras que Java implementa el polimorfismo. Si nunca ha utilizado un lenguaje que permite la sobrecarga de métodos y, a continuación, el concepto puede parecer extraño al principio. Pero como puede ver, sobrecarga de métodos es una de las características más interesantes y útiles de Java. Cuando se invoca un método sobrecargado, Java utiliza el tipo o el número de argumentos como su guía para determinar la versión del método sobrecargado realmente llamar. Por lo tanto, los métodos sobrecargados deben ser diferente en el tipo o el número de sus parámetros. Aunque los métodos sobrecargados pueden tener diferentes tipos de devolución, el tipo devuelto por
sí sola no basta distinguir dos versiones de un método. Cuando Java encuentra una llamada a un método sobrecargado, simplemente ejecuta la versión del método cuyos parámetros coincidan con los argumentos utilizados en la llamada. Aquí está un ejemplo sencillo que ilustra la sobrecarga de métodos:


// Demostrando la sobrecarga de métodos.

class
OverloadDemo {


void test()
{


System.out.println("No
parameters");


}


// Overload
test for one integer parameter.


void
test(int a) {


System.out.println("a:
" + a);


}


// Overload
test for two integer parameters.


void
test(int a, int b) {


System.out.println("a
and b: " + a + " " + b);


}


// overload
test for a double parameter


double
test(double a) {


System.out.println("double
a: " + a);


return a*a;


}


}


class
Overload {


public
static void main(String args[]) {


OverloadDemo
ob = new OverloadDemo();


double
result;


// call all
versions of test()


ob.test();


ob.test(10);


ob.test(10,
20);


result =
ob.test(123.25);


System.out.println("Result
of ob.test(123.25): " + result);


}


}


Este programa genera el siguiente resultado:


No
parameters


a: 10


a and b: 10
20


double a:
123.25


Result of
ob.test(123.25): 15190.5625


Como puede ver, (de prueba) está sobrecargado cuatro veces.
La primera versión no toma parámetros, la segunda toma un parámetro de número
entero, la tercera toma dos parámetros de enteros, y la cuarta toma un
parámetro doble. El hecho de que la cuarta versión de prueba () también
devuelve un valor es sin consecuencias respecto a la sobrecarga, ya que tipos
de devolución no desempeñar un papel en la resolución de sobrecarga. Cuando se
llama a un método sobrecargado, Java busca una coincidencia entre los
argumentos utilizados para llamar el método y los parámetros del método. Sin
embargo, este partido no siempre necesita ser exacto. En algunos casos las
conversiones de tipo automático de Java pueden desempeñar un papel en la
resolución de sobrecarga. Por
ejemplo, considere el siguiente programa:


//
Automatic type conversions apply to overloading.


class
OverloadDemo {


void test()
{


}


// Overload
test for two integer parameters.


void
test(int a, int b) {


System.out.println("a
and b: " + a + " " + b);


}


// overload
test for a double parameter


void
test(double a) {


System.out.println("Inside
test(double) a: " + a);


}


}


class
Overload {


public
static void main(String args[]) {


OverloadDemo
ob = new OverloadDemo();


int i = 88;


ob.test();


ob.test(10,
20);


ob.test(i);
// this will invoke test(double)


ob.test(123.2);
// this will invoke test(double)


}


}


Este programa genera el siguiente resultado:


No
parameters


a and b: 10
20


Inside
test(double) a: 88


Inside
test(double) a: 123.2


Como puede ver, esta versión de OverloadDemo no define
test(int). Por lo tanto, cuando se llama a prueba () con un argumento entero
dentro de sobrecarga, no se encuentra ningún método coincidente. Sin embargo,
Java puede convertir automáticamente un número entero en un doble, y esta
conversión puede utilizarse para resolver la llamada. Por lo tanto, después de
que no se encuentra test(int), Java me eleva a doble y, a continuación, llama
test(double). Por supuesto, si test(int) se había definido, se habría llamado
en su lugar. Java empleará sus conversiones de tipo automático sólo si no se
encuentra ninguna coincidencia exacta. Sobrecarga de métodos soporta
polimorfismo porque es una manera de que Java implementa el paradigma de
"una interfaz, varios métodos". Para entender cómo, considere lo
siguiente. En lenguajes que no admiten la sobrecarga de métodos, debe darse un
nombre único a cada método. Sin embargo, con frecuencia se desea implementar
esencialmente el mismo método para diferentes tipos de datos. Considérese la
función valor absoluto. En lenguajes que no admiten la sobrecarga, generalmente
hay tres o más versiones de esta función, cada uno con un nombre ligeramente
diferente. Por ejemplo, en el C, el abs de función () devuelve el valor
absoluto de un número entero, laboratorios () devuelve el valor absoluto de un
entero largo y fabulosos () devuelve el valor absoluto de un valor de coma
flotante. Ya c no admite la sobrecarga, cada función debe tener su propio nombre,
a pesar de todas las funciones de tres es esencialmente la misma cosa.


Esto hace la situación más compleja, conceptualmente, lo que
realmente es. Aunque el concepto subyacente de cada función es el mismo,
todavía tiene tres nombres para recordar. Esta situación se produce en Java,
porque cada método de valor absoluto puede utilizar el mismo nombre. De hecho,
la biblioteca de clase estándar de Java incluye un método de valor absoluto,
llamado abs (). Este método está sobrecargado por la clase de matemáticas de
Java para manejar todos los tipos numéricos. Java determina qué versión de abs
() para llamar a basada en el tipo de argumento. El valor de la sobrecarga es
que permite que los métodos relacionados a ser visitada por el uso de un nombre
común. Así, el nombre abs representa la acción de carácter general que se está
realizando. Se deja al compilador para elegir la versión derecho específica
para una circunstancia particular. Usted, el programador, necesita sólo
recordar la operación general que se realiza. A través de la aplicación de
polimorfismo, varios nombres han sido reducidos a uno. Aunque este ejemplo es
bastante simple, que si expande el concepto, puede ver cómo puede ayudar la
sobrecarga, administrar la mayor complejidad.


 Cuando usted sobrecarga
un método, cada versión de dicho método puede realizar cualquier actividad que
desee. No hay regla que indica que los métodos sobrecargados deben relacionar
entre sí. Sin embargo, desde un punto de vista estilístico, la sobrecarga de
métodos implica una relación. Así, mientras que se puede utilizar el mismo
nombre para sobrecargar métodos no relacionados, usted no debe. Por ejemplo, se
podría utilizar el nombre sqr para crear métodos que devuelven el cuadrado de
un número entero y la raíz cuadrada de un valor de coma flotante. Pero estas
dos operaciones son fundamentalmente diferentes. Aplicar el método de la
sobrecarga en derrotas de esta manera su propósito original. En la práctica,
sólo se deben sobrecargar las operaciones estrechamente relacionadas.


Sobrecarga de Constructors.


Además de la sobrecarga de métodos normales, también puede
sobrecargar métodos de constructor. De hecho, para la mayoría las clases reales
que cree, constructores sobrecargados será la norma, no la excepción. Para
entender por qué, volvamos a la clase de cuadro desarrollada en el capítulo
anterior. Lo que sigue es la última versión del cuadro:


class Box {


double
width;


double
height;


double
depth;


// This is
the constructor for Box.


Box(double
w, double h, double d) {


width = w;


height = h;


depth = d;


}


// compute
and return volume


double
volume() {


return
width * height * depth;


}


}


Como se puede ver, el Box( ) constructor requiere tres
parámetros. Esto significa que todas las declaraciones de un cuadro de objetos
deben pasar tres argumentos para el constructor de box (). Por ejemplo, la
siguiente declaración es actualmente no válida:





Box ob =
new Box();


Box( ) requiere tres argumentos, es un error llamarlo sin
ellos. Esto plantea algunas cuestiones importantes. ¿Qué sucede si simplemente
quería un cuadro y no cuidado (o saber) lo que fueron sus dimensiones
iniciales? O, ¿qué sucede si desea poder inicializar un cubo mediante la
especificación de un único valor que se utilizaría para las tres dimensiones?
Como la clase de cuadro actualmente está escrita, estas otras opciones no están
disponibles para usted. Afortunadamente, la solución a estos problemas es muy fácil:
simplemente de sobrecarga del constructor de cuadro para que maneja las
situaciones que se acaba de describir. Aquí es un programa que contiene una
versión mejorada del cuadro que hace exactamente eso:





/* Here,
Box defines three constructors to initialize


the
dimensions of a box various ways.


*/


class Box {


double
width;


double
height;


double
depth;


//
constructor used when all dimensions specified


Box(double
w, double h, double d) {


width = w;


height = h;


depth = d;


}


//
constructor used when no dimensions specified


Box() {


width =
-1;  // use -1 to indicate


height =
-1; // an uninitialized


depth =
-1;  // box


}


//
constructor used when cube is created


Box(double
len) {


width =
height = depth = len;


}


// compute
and return volume


double
volume() {


return
width * height * depth;


}


}


class
OverloadCons {


public
static void main(String args[]) {


// create
boxes using the various constructors


Box mybox1
= new Box(10, 20, 15);


Box mybox2
= new Box();


Box mycube
= new Box(7);


double vol;


// get
volume of first box


vol =
mybox1.volume();


System.out.println("Volume
of mybox1 is " + vol);


// get
volume of second box


vol =
mybox2.volume();


System.out.println("Volume
of mybox2 is " + vol);


// get
volume of cube


vol =
mycube.volume();


System.out.println("Volume
of mycube is " + vol);


}


}


Aquí se muestra la salida producida por este programa:


Volume of
mybox1 is 3000.0


Volume of
mybox2 is -1.0


Volume of
mycube is 343.0


Como puede ver, se llama al constructor sobrecargado
adecuado en función de los parámetros especificados cuando se ejecuta en nuevo.


Uso de objetos como parámetros


Hasta ahora hemos sólo se ha utilizando tipos simples como
parámetros a los métodos. Sin embargo, es correcto y común para pasar objetos a
métodos. Por ejemplo,
considere el siguiente programa corto:


// Objects
may be passed to methods.


class Test
{


int a, b;


Test(int i,
int j) {


a = i;


b = j;


}


// return
true if o is equal to the invoking object


boolean
equals(Test o) {


if(o.a == a
&& o.b == b) return true;


else return
false;


}


}


class
PassOb {


public
static void main(String args[]) {


Test ob1 =
new Test(100, 22);


Test ob2 =
new Test(100, 22);


Test ob3 =
new Test(-1, -1);


System.out.println("ob1
== ob2: " + ob1.equals(ob2));


System.out.println("ob1
== ob3: " + ob1.equals(ob3));


}


}


Este programa genera el siguiente resultado:


ob1 == ob2:
true


ob1 == ob3:
false




Como puede ver, el método equals de () dentro de prueba
compara dos objetos para la igualdad y devuelve el resultado. Es decir, compara
el objeto invocando con uno que se pasa. Si contienen los mismos valores, el
método devuelve true. De lo contrario, devuelve false. Observe que el parámetro
o es igual a () especifica la prueba como su tipo. Aunque la prueba es un tipo
de clase creado por el programa, se utiliza en sólo del mismo modo que los
tipos integrados de Java. Uno de los usos más comunes de los parámetros del
objeto implica constructores. Frecuentemente se desea construir un nuevo
objeto, por lo que inicialmente es el mismo que algún objeto existente. Para
ello, debe definir un constructor que toma un objeto de su clase como un
parámetro. Por ejemplo, la siguiente versión del cuadro de permite que un
objeto inicializar otro:


// Here,
Box allows one object to initialize another.


class Box {


double
width;


double
height;


double
depth;


//
construct clone of an object


Box(Box ob)
{ // pass object to constructor


width =
ob.width;


height =
ob.height;


depth =
ob.depth;


}


//
constructor used when all dimensions specified


Box(double
w, double h, double d) {


width = w;


height = h;


depth = d;


}


//
constructor used when no dimensions specified


Box() {


width =
-1;  // use -1 to indicate


height = -1;
// an uninitialized


depth =
-1;  // box


}


//
constructor used when cube is created


Box(double
len) {


width =
height = depth = len;


}


// compute
and return volume


double
volume() {


return
width * height * depth;


}


}


class
OverloadCons2 {


public
static void main(String args[]) {


// create
boxes using the various constructors


Box mybox1
= new Box(10, 20, 15);


Box mybox2
= new Box();


Box mycube
= new Box(7);


Box myclone
= new Box(mybox1);


double vol;


// get
volume of first box


vol =
mybox1.volume();


System.out.println("Volume
of mybox1 is " + vol);


// get
volume of second box


vol =
mybox2.volume();


System.out.println("Volume
of mybox2 is " + vol);


// get
volume of cube


vol =
mycube.volume();


System.out.println("Volume
of cube is " + vol);


// get volume
of clone


vol =
myclone.volume();


System.out.println("Volume
of clone is " + vol);


}


}

Como puede ver cuando comience a crear sus propias clases,
proporcionando muchas formas de métodos de constructor normalmente es necesario
para permitir que los objetos que se construirán en una manera conveniente y
eficiente.




Un vistazo a pasar  argumento.



En general, hay dos maneras de que un lenguaje de
programación puede pasar un argumento a una subrutina. La primera es llamada
por valor. Este método copia el valor de un argumento en el parámetro formal de
la subrutina. Por lo tanto, los cambios realizados en el parámetro de la
subrutina no tienen efecto sobre el argumento. La segunda manera que puede
pasar un argumento es llamada por referencia. En este método, se pasa una
referencia a un argumento (no el valor del argumento) para el parámetro. Dentro
de la subrutina, esta referencia se utiliza para acceder el argumento real
especificado en la llamada. Esto significa que los cambios realizados en el
parámetro afectará el argumento que se utiliza para llamar a la subrutina. Como
puede ver, Java utiliza ambos enfoques, en función de lo que se pasa. En Java,
al pasar un tipo simple a un método, se pasa por valor. Por lo tanto, lo que se
produce al parámetro que recibe el argumento no tiene efecto fuera del método. Por ejemplo, considere el siguiente
programa:


// Simple
types are passed by value.


class Test
{


void
meth(int i, int j) {


i *= 2;


j /= 2;


}


}


class
CallByValue {


public
static void main(String args[]) {


Test ob =
new Test();


int a = 15,
b = 20;


System.out.println("a
and b before call: " +


a + "
" + b);


ob.meth(a,
b);


System.out.println("a
and b after call: " +


a + "
" + b);


}


}


La salida de este programa se muestra aquí:


a and b
before call: 15 20


a and b
after call: 15 20


Como puede ver, las operaciones que se producen dentro de
meth() no tienen ningún efecto en los valores de un y b utilizado en la
llamada; sus valores aquí no cambió a 30 y 10. Cuando se pasa un objeto a un
método, la situación cambia drásticamente, debido a que los objetos se pasan
por referencia. Tenga en cuenta que cuando se crea una variable de un tipo de
clase, sólo se crea una referencia a un objeto. Así, cuando se pasa esta
referencia a un método, el parámetro que recibe se referirá al mismo objeto
como la que se refirió el argumento. Esto significa que los objetos se pasan a los
métodos por uso de llamada por referencia. Cambios en el objeto dentro del
método afectan al objeto que se utiliza como argumento. Por ejemplo, considere
el siguiente programa:


// Objects
are passed by reference.


class Test
{


int a, b;


Test(int i,
int j) {


a = i;


b = j;


}


// pass an
object


void
meth(Test o) {


o.a *=  2;


o.b /= 2;


}


}


class
CallByRef {


public
static void main(String args[]) {


Test ob =
new Test(15, 20);


System.out.println("ob.a
and ob.b before call: " +


ob.a +
" " + ob.b);


ob.meth(ob);


System.out.println("ob.a
and ob.b after call: " +


ob.a +
" " + ob.b);


}


}


Este programa genera el siguiente resultado:


ob.a and
ob.b before call: 15 20


ob.a and
ob.b after call: 30 10


Como puede ver, en este caso, las acciones () de
metanfetaminas han afectado el objeto utilizado como argumento. Como un punto
de interés, cuando una referencia de objeto se pasa a un método, la propia
referencia se pasa por el uso de la llamada por valor. Sin embargo, dado que el
valor que se pasa se refiere a un objeto, la copia de ese valor todavía se
referirá al mismo objeto que hace de su argumento correspondiente.


Returning Objects


Un método puede devolver cualquier tipo de datos, incluidos
los tipos de clase que se crean. Por ejemplo, el siguiente programa, el método
de incrByTen () devuelve un objeto en el que el valor de un diez es mayor que
en el objeto invocando.


// Returning
an object.


class Test
{


int a;


Test(int i)
{


a = i;


}


Test
incrByTen() {


Test temp =
new Test(a+10);


return
temp;


}


}


class RetOb
{


public
static void main(String args[]) {


Test ob1 =
new Test(2);


Test ob2;


ob2 =
ob1.incrByTen();


System.out.println("ob1.a:
" + ob1.a);


System.out.println("ob2.a:
" + ob2.a);


ob2 =
ob2.incrByTen();


System.out.println("ob2.a
after second increase: "


+ ob2.a);


}


}


Aquí se muestra la salida generada por este programa:


ob1.a: 2


ob2.a: 12


ob2.a after
second increase: 22


Como puede ver, se invoca cada () incrByTen de tiempo, se
crea un nuevo objeto y, a continuación, se devuelve una referencia a la misma a
la rutina de llamada. El programa anterior hace otro punto importante: ya que
todos los objetos se asignan dinámicamente utilizando de nuevo, no es necesario
preocuparse por un objeto que se va fuera de ámbito, porque termina el método
en el que fue creado. El objeto seguirá existiendo mientras hay una referencia
a él en alguna parte de su programa. Cuando no hay referencias a él, el objeto
se ser reclamado la próxima vez que lleva a cabo la recolección


Recursion.


 Java soporta la
recursividad. Recursión es el proceso de definir algo de sí mismo. Lo que se
refiere a la programación Java, recursión es el atributo que permite un método
para llamar a sí mismo. Un método que se llama a sí mismo se dice que es
recursiva. El ejemplo clásico de la recursividad es el cómputo del factorial de
un número. El factorial de un número n es el producto de todos los números
enteros entre 1 y N. Por ejemplo, 3 factorial es 1 × 2 × 3 o 6. Aquí es cómo
puede calcularse un factorial por uso de un método recursivo:





// A simple
example of recursion.


class
Factorial {


// this is
a recursive function


int
fact(int n) {


int result;


if(n==1)
return 1;


result =
fact(n-1) * n;


return
result;


}


}


class
Recursion {


public
static void main(String args[]) {


Factorial f
= new Factorial();


System.out.println("Factorial
of 3 is " + f.fact(3));


System.out.println("Factorial
of 4 is " + f.fact(4));


System.out.println("Factorial
of 5 is " + f.fact(5));


}


}


La salida de este programa se muestra aquí:


Factorial
of 3 is 6


Factorial
of 4 is 24


Factorial of 5 is 120


Si no está familiarizado con los métodos recursiva, la
operación de hecho () puede parecer un poco confusa. Aquí es cómo funciona.
Cuando se llama (de hecho) con un argumento de 1, la función devuelve 1; de lo
contrario devuelve el producto de hecho (n–1) * n. Para evaluar esta expresión,
(de hecho) se llama con n–1. Este proceso se repite hasta que n es igual a 1 y
las llamadas al método comienzan a regresar.


Para entender mejor cómo funciona el método () de hecho,
vamos a través de un ejemplo corto. Cuando se calcula el factorial de 3, la
primera llamada a (de hecho) hará una segunda llamada a hacerse con un
argumento de 2. Esta invocación causará (de hecho) a ser llamada una tercera
vez con un argumento de 1. Esta llamada devuelve 1, que luego se multiplica por
2 (el valor de n en la invocación del segunda). Este resultado (2), a
continuación, se volvió a la invocación original de (de hecho) y multiplicado
por 3 (el valor original de n). Esto da la respuesta, 6. Puede ser interesante
insertar las declaraciones de Console.println () en (de hecho) que mostrará el
nivel de cada llamada y cuáles son las respuestas intermedias.


Cuando un método llama a sí mismo, parámetros y variables
locales nuevas están asignados a almacenamiento de información en la pila y se
ejecuta el código del método con estas nuevas variables desde el principio. Una
llamada recursiva no hace una copia nueva del método. Sólo los argumentos son
nuevos. Como cada llamada recursiva devuelve, los parámetros y variables
locales antiguas se quitan de la pila y se reanuda la ejecución en el momento
de la llamada dentro del método. Métodos de recursiva pueden decirse que
"telescopio" fuera y de vuelta. Versiones de recursiva de muchas
rutinas pueden ejecutar un poco más lentamente que el equivalente iterativo
debido a la mayor sobrecarga de las llamadas a funciones adicionales. Muchas
llamadas recursivas a un método podrían causar un desbordamiento de la pila.
Porque es de almacenamiento para los parámetros y las variables locales en la
pila y cada nueva llamada crea una nueva copia de estas variables, es posible
que la pila podría estar agotada. Si esto ocurre, el sistema de tiempo de
ejecución de Java producirá una excepción. Sin embargo, probablemente no tendrá
que preocuparse por esto a menos que una rutina recursiva se ejecutaen forma nativa.

No hay comentarios:

Publicar un comentario