Cuestionario
Cuestionario
Cuestionario
Después de declarar una clase se deben implementar (escribir el código de) sus
funciones y procedimientos, así como su constructor y su destructor.
El código de las funciones y procedimientos de una clase se escribe igual que el de las
funciones y procedimientos habituales, teniendo únicamente en cuenta que el nombre de
la función o procedimiento que se debe indicar será:
Nombre_Clase.Nombre_Procedimiento_o_Funcion
//=============
TYPE
//=============
TPregunta = clase
protegido
_CodPregunta: cadena;
_CodCampo: cadena;
funcion _LeeValor(): variante;
procedimiento _EscribeValor(_valor: variante);
publico
constructor Crear(p, c: cadena);
destructor Destruir();
procedimiento Pregunta();
propiedad valor: variante leer _LeeValor
escribir _EscribeValor;
fin;
//=============================
// IMPLEMENTACION DE TPregunta
//=============================
//------------------------------------------------------------------
constructor TPregunta.Crear(p, c: cadena);
//------------------------------------------------------------------
inicio
_CodPregunta := p;
_CodCampo := c;
fin;
//------------------------------------------------------------------
destructor TPregunta.Destruir();
//------------------------------------------------------------------
inicio
// Nada a hacer
fin;
//------------------------------------------------------------------
funcion TPregunta._LeeValor(): variante;
//------------------------------------------------------------------
inicio
resultado := ValorProspeccion(_CodCampo);
fin;
//------------------------------------------------------------------
procedimiento TPregunta._EscribeValor(_valor: variante);
//------------------------------------------------------------------
inicio
AsignarValorProspeccion(_CodCampo, _valor);
fin;
//------------------------------------------------------------------
procedimiento TPregunta.Pregunta();
//------------------------------------------------------------------
inicio
Preguntar(_CodPregunta);
fin;
//================================================================
=
procedimiento DestruyePreguntas( pregs: tabla de TPregunta);
//================================================================
=
//
// Destruye un array de TPregunta, una a una.
//
var
i: entero;
inicio
//====
VAR
//====
Nombre, Apellido1, Apellido2, NombreCompleto: TPregunta;
// Preguntas de la prospección
//=================
// CODIGO PRINCIPAL
//=================
INICIO
FinProspeccion(Cierto, '1');
FIN.
Otros ejemplos
En este primer ejemplo adicional, se declara e implementa una clase cuya utilidad sería
poder leer los registros de un cierto fichero de productos de un determinado proyecto:
(...)
TIPO
TProducto = Clase
Privado
_RegId: entero;
_HaCambiado: booleano;
funcion _ValorCodigo(): cadena;
funcion _ValorDescripcion(): cadena;
funcion _ValorFamilia(): entero;
funcion _ValorPrecio(): real;
funcion _NoSeleccionar(): booleano;
funcion _ValorOID(): entero;
funcion _LeerHaCambiado(): booleano;
procedimiento _AsignarOID(_OID: entero);
Publico
constructor Crear();
destructor Destruir();
//***************************************//
//** Implementación de clase TProducto **//
//***************************************//
constructor TProducto.Crear();
inicio
_RegId := 0;
fin;
destructor TProducto.Destruir();
inicio
Si _RegId <> 0 entonces
LiberarRegistro(_RegId);
fin;
_HaCambiado := Cierto;
fin;
(...)
En este segundo ejemplo, se declara e implementa una clase que permite trabajar con
parejas de enteros, que pueden opcionalmente mantenerse ordenadas:
TIPO
TParejaEnteros = clase
Privado
procedimiento _Intercambia();
Protegido
_a, _b: entero;
_asignada: booleano;
_ordenar: booleano;
procedimiento InterCambia();
procedimiento Asigna(xx, yy: entero);
procedimiento Acerar();
funcion EsIgual( p: TParejaEnteros): booleano;
propiedad a: entero leer _a;
propiedad b: entero leer _b;
propiedad Ordenada: booleano leer _Ordenar
escribir _AsignaOrdenar;
fin;
constructor TParejaEnteros.Crear();
inicio
_a := 0;
_b := 0;
_ordenar := falso;
fin;
destructor TParejaEnteros.Destruir();
inicio
// Nada a hacer
fin;
procedimiento TParejaEnteros._Intercambia();
var
c: entero;
inicio
c := _b;
_b := _a;
_a := c;
fin;
procedimiento TParejaEnteros.InterCambia();
inicio
si NO _ordenar entonces _InterCambia();
fin;
fin;
procedimiento TParejaEnteros.Acerar();
inicio
_a := 0;
_b := 0;
fin;
INICIO
...
FIN.
Ahora que ya sabemos como crear, utilizar y destruir objetos, es hora de aprender cómo
escribir clases de las que crear esos objetos.
Una clase es un proyecto o prototipo que se puede utilizar para crear muchos objetos. La
implementación de una clase comprende dos componentes: la declaración y el cuerpo de
la clase.
DeclaraciónDeLaClase {
CuerpoDeLaClase
La Declaración de la Clase
Como mínimo, la declaración de una clase debe contener la palabra clave class y el
nombre de la clase que está definiendo. Así la declaración más sencilla de una clase se
parecería a esto.
class NombredeClase {
...
}
Por ejemplo, esta clase declara una nueva clase llamada NumeroImaginario.
class NumeroImaginario {
...
Los nombres de las clases deben ser un identificador legal de Java y, por convención,
deben empezar por una letra mayúscula. Muchas veces, todo lo que se necesitará será
una declaración mínima. Sin embargo, la declaración de una clase puede decir más
cosas sobre la clase. Más especificamente, dentro de la declaración de la clase se puede.
En Java, todas las clases tienen una superclase. Si no se especifica una superclase para
una clase, se asume que es la clase Object (declarada en java.lang). Entonces la
superclase de NumeroImaginario es Object porque la declaración no explicitó ninguna
otra clase. Para obtener más información sobre la clase Object, puede ver La clase
Object.
...
Por ejemplo, supon que quieres que la superclase de NumeroImaginario sea la clase
Number en vez de la clase Object. Se podría escribir esto.
...
Cuando se declara una clase, se puede especificar que interface, si lo hay, está
implementado por la clase. Pero, ¿Qué es un interface? Un interface declara un conjunto
de métodos y constantes sin especificar su implementación para ningún método. Cuando
una clase exige la implementación de un interface, debe proporcionar la implementación
para todos los métodos declarados en el interface.
Para declarar que una clase implementa uno o más interfaces, se debe utilizar la palabra
clave implements seguida por una lista de los interfaces implementados por la clase
delimitados por comas. Por ejemplo, imagina un interface llamado Aritmetico que define
los métodos llamados suma(), resta(), etc... La clase NumeroImaginario puede declarar
que implementa el interface Aritmetico de esta forma.
...
se debe garantizar que propociona la implementación para los métodos suma(), resta() y
demás métodos declarados en el interface Aritmetico. Si en NumeroImaginario falta
alguna implementación de los métodos definidos en Aritmetico, el compilador mostrará un
mensaje de error y no compilará el programa.
Observa que las firmas de los métodos declarados en el interface Aritmetico deben
corresponder con las firmas de los métodos implementados en la clase
NumeroImaginario. Tienes más información sobre cómo crear y utilizar interfaces en
Crear y Utilizar Interfaces.
Se puede utilizar uno de estos tres modificadores en una declaración de clase para
declarar que esa clase es pública, abstracta o final. Los modificadores van delante de la
palabra clave class y son opcionales.
El modificador public declara que la clase puede ser utilizada por objetos que estén fuera
del paquete actual. Por defecto, una clase sólo puede ser utilizada por otras clases del
mismo paquete en el que están declaradas.
...
Por convención, cuando se utiliza la palabra public en una declaración de clase debemos
asegurarnos de que es el primer item de la declaración.
El modificador abstract declara que la clase es una clase abstracta. Una clase abstracta
podría contener métodos abstractos (métodos sin implementación). Una clase abstracta
está diseñada para ser una superclase y no puede ejemplarizarse. Para una discusión
sobre las clases abstractas y cómo escribirlas puedes ver Escribir Clases y Métodos
Abstractos.
Utilizando el modificador final se puede declarar que una clase es final, que no puede
tener subclases. Existen (al menos) dos razones por las que se podría querer hacer esto:
razones de seguridad y razones de diseño. Para una mejor explicación sobre las clases
finales puedes ver Escribir Clases y Métodos Finales.
Observa que no tiene sentido para una clase ser abstracta y final. En otras palabras, una
clase que contenga métodos no implementados no puede ser final. Intentar declarar una
clase como final y abstracta resultará en un error en tiempo de compilación.
[ implements NombredeInterface ] {
...
Los puntos entre [ y ] son opcionales. Una declaración de clase define los siguientes
aspectos de una clase.
De todos estos items, sólo la palabra clave class y el nombre de la clase son necesarios.
Los otros son opcionales. Si no se realiza ninguna declaración explícita para los items
opcionales, el compilador Java asume ciertos valores por defecto (una subclase de Object
no final, no pública, no obstracta y que no implementa interfaces).
Modificadores
septiembre
Los modificadores son palabras clave que le entregan al compilador información acerca
de la naturaleza del código, datos o clases. Existen dos grandes grupos de
modificadores, los mas comunes son los modificadores de acceso public, protected y
private dejando en un segundo gran grupo otros como como final, abstract, static, native,
transient, synchronized, volatile..
public - Modificador de acceso que hace que un método o variable sea accesible para
cualquier otra clase.
protected - Modificador de acceso que hace que un método o variable sea accesible solo
por las clases que se encuentran en el mismo paquete o por las clases hijas
de la clase que lo contiene.
private - Modificador de acceso que hace que un método o variable sea accesible solo
para la clase que lo contiene.
final - Hace que sea imposible de extender, sobrecargar un
método o modificar el valor de una variable.
abstract - Palabra clave utilizada para declarar que una clase no puede ser instanciada o
para declarar que un método debe ser implementado en una clase hija no
abstracta.
static - Hace un método o variable relativo a la
clase y no a la instancia.
native - Indica que un método esta escrito en un lenguaje dependiente de la plataforma.
transient - Previene que un campo sea serializado.
synchronized - Indica que un método solo puede ser accedido por un hilo a la vez.
volatile - Indica que una variable debe ser leída siempre directamente de la
memoria principal. De esta forma se evita que se realicen copias locales al
hilo que puedan diferir entre si.
class - Palabra clave utilizada para definir una clase.
int - Tipo de dato de 32 bits entero con signo que puede tomar valores de -231 a 231-1.
System - Esta clase, que no puede ser instanciada, contiene muchas campos y
métodos útiles entre los cuales se encuentran los flujos de entrada y
salida estándar, la salida de error, el acceso a propiedades definidas
externamente, medios para cargar ficheros y librerías y un método para
rápidamente copiar una parte de un arreglo.
cadena de caracteres - "Tengo "
Native no no yes no No
transient no yes no no No
volatile no yes no no No
Las clases y métodos van entre llaves {} y al final de una instrucción o declaración de
variable debe escribirse un punto y coma (;). Se utilizan tabuladores para sangrar las
líneas de código con el fin de facilitar su legibilidad, aunque si se omiten no pasa nada.
Ejemplo:
Por consola:
Hola Jesus
• Objetivos del método main: mostrar un mensaje por consola, crear un objeto
de la clase PrimerSaludo, invocar al método mostrarMensaje mediante el objeto
anterior y mostrar otro mensaje por consola. Aparte de estos objetivos, el
fundamental y más importante es servir como punto de inicio de la ejecución del
programa.
El programador, aparte de crear sus propios métodos como en el código anterior, puede
utilizar los que forman parte de la API (Application Programming Interface) estándar de
Java. La API se estudiará en siguiente sección.
• void. Es lo que debe ponerse cuando el método no devuelve nada y, por ejemplo,
simplemente muestra por consola un mensaje. Es el caso del método "void
mostrarMensaje()" del código anterior.
Modificadores de acceso
Los modificadores de C# son bastante similares a los de Java, con varias diferencias
pequeñas. Cada miembro de una clase, o la propia clase, se puede declarar con un
modificador de acceso para definir el ámbito de acceso permitido. Las clases no
declaradas dentro de otras clases sólo pueden especificar los modificadores públicos o
internos. Las clases anidadas, como otros miembros de clase, pueden especificar
cualquiera de los cinco modificadores de acceso siguientes:
• public
• private
• internal
• protected internal
Modificador internal
A un elemento internal sólo se puede tener acceso desde dentro del ensamblado actual.
Un ensamblado de .NET Framework es casi equivalente a un archivo JAR de Java;
representa las unidades de creación a partir de las cuales se pueden crear otros
programas.
Modificador sealed
Una clase con el modificador sealed en la declaración de clase es lo opuesto a una clase
abstracta: no se puede heredar. Puede marcar una clase como sealed para evitar que
otras clases reemplacen su funcionalidad. Naturalmente, una clase con el modificador
sealed no puede ser abstracta. Observe también que a una estructura se le aplica
implícitamente el modificador sealed; por consiguiente, no se puede heredar. El
modificador sealed es equivalente a marcar una clase con la palabra clave final en Java.
Modificador readonly
Para definir una constante en C#, utilice el modificador const o readonly en lugar de la
palabra clave final de Java. El factor distintivo entre los dos modificadores de C# es que
los elementos const se tratan en tiempo de compilación, mientras que valores de los
campos readonly se especifican en tiempo de ejecución. Esto significa que la asignación
a los campos readonly se puede producir tanto en el constructor de clase como en la
declaración. Por ejemplo, la clase siguiente declara una variable readonly denominada
IntegerVariable que se inicializa en el constructor de clase:
class SampleClass
{
public int x; // No access restrictions.
}
.
// protected_public.cs
// Public access
using System;
class Point
{
public int x;
public int y;
}
class MainClass
{
static void Main()
{
Point p = new Point();
// Direct access to public members:
p.x = 10;
p.y = 15;
Console.WriteLine("x = {0}, y = {1}", p.x, p.y);
}
}
Resultado
x = 10, y = 15
Si se cambia el nivel de acceso de public a private o protected, se aparecerá el siguiente
mensaje de error:
Un miembro protegido de una clase base es accesible en una clase derivada sólo si el
acceso se realiza a través del tipo de la clase derivada. Por ejemplo, considere el
siguiente segmento de código:
// protected_keyword.cs
using System;
class A
{
protected int x = 123;
}
class B : A
{
static void Main()
{
A a = new A();
B b = new B();
Ejemplo
En este ejemplo, la clase DerivedPoint se deriva de Point; por lo tanto, puede obtener
acceso a los miembros protegidos de la clase base directamente desde la clase derivada.
// protected_keyword_2.cs
using System;
class Point
{
protected int x;
protected int y;
}
class Employee
{
private int i;
double d; // private access by default
}
Los tipos anidados del mismo cuerpo también pueden tener acceso a esos miembros
privados.
En este ejemplo, la clase Employee contiene dos miembros de datos privados, name y
salary. Como miembros privados, sólo pueden tener acceso a ellos los métodos miembro;
por tanto, hay que agregar los métodos públicos denominados GetName y Salary para
permitir el acceso controlado a los miembros privados. Se tiene acceso al miembro name
a través del método público y se tiene acceso al miembro salary a través de una
propiedad pública de sólo lectura.
// private_keyword.cs
using System;
class Employee
{
private string name = "FirstName, LastName";
private double salary = 100.0;
class MainClass
{
static void Main()
{
Employee e = new Employee();
Modificadores de acceso
Los modificadores de acceso sirven para restringir el acceso a los campos o a los
métodos de una clase.
• private
• protected
• public
El modificador private permite que el campo o método sólo pueda ser accedido dentro de
la clase donde fue declarado.
El modificador protected permite que el campo o método sólo pueda ser accedido dentro
de la clase donde fue declarado y en todas las clases derivadas de ella.
El modificador public permite que el campo o método pueda ser accedido desde cualquier
clase.
Las clases permiten implementar tipos de datos abstractos. El problema que se presenta
es que desde cualquier clase se puede accesar los campos de un objeto perteneciente a
otra clase. Esto es una violación al principio de abstracción que dice que un tipo de datos
abstracto sólo puede ser manipulado a través de las operaciones que se definen para
éste.
En java al definir una clase se puede controlar la visibilidad que tendrán sus campos y
métodos al exterior de la clase. Este control se efectúa de la siguiente forma:
class A
{
private int privx;
protected int protb;
public int pubc;
int paqd;
Ejemplo:
class B
{
public void MetB()
{
A a= new A();
a.pubc= 1; // Ok
a.priva= 2; // error, privado
a.protb= 3; // error, B no es
// subclase de A
a.MetPub(); // Ok
a.MetPriv(); // error, privado
}
}
Visibilidad de Clases
Al declarar una clase se puede especificar que es pública usando el atributo public. De
este modo la clase podrá ser usada por cualquier otra clase. Si la clase no es pública
entonces la clase sólo puede ser usada dentro del paquete que la contiene.
El nombre de archivo
Un archivo puede contener a lo más una clase pública, en cuyo caso el nombre del
archivo debe ser el mismo de la clase pública, más la extensión .java. El resto de las
clases del archivo sólo serán visibles dentro del paquete. Es usual que los archivos
contengan una sola clase, pero también a veces se agregan otras clases al archivo
cuando éstas son usadas sólo dentro de ese mismo archivo.
En programación modular, y más específicamente en programación orientada a objetos,
se denomina encapsulamiento al ocultamiento del estado, es decir, de los datos
miembro, de un objeto de manera que sólo se puede cambiar mediante las operaciones
definidas para ese objeto.
Cada objeto está aislado del exterior, es un módulo natural, y la aplicación entera se
reduce a un agregado o rompecabezas de objetos. El aislamiento protege a los datos
asociados a un objeto contra su modificación por quien no tenga derecho a acceder a
ellos, eliminando efectos secundarios e interacciones.
Encapsulamiento
Como se puede observar de los diagramas, las variables del objeto se localizan en el
centro o núcleo del objeto. Los métodos rodean y esconden el núcleo del objeto de otros
objetos en el programa. Al empaquetamiento de las variables de un objeto con la
protección de sus métodos se le llama encapsulamiento. Típicamente, el encapsulamiento
es utilizado para esconder detalles de la puesta en práctica no importantes de otros
objetos. Entonces, los detalles de la puesta en práctica pueden cambiar en cualquier
tiempo sin afectar otras partes del programa.
Formas de encapsular
1. Estándar (Predeterminado)
2. Abierto : Hace que el miembro de la clase pueda ser accedido desde el exterior
de la Clase y cualquier parte del programa.
3. Protegido : Solo es accesible desde la Clase y las clases que heredan (a
cualquier nivel).
4. Cerrado : Solo es accesible desde la Clases.
En el encapsulamiento hay analizadores que pueden ser semánticos y sintácticos
class Fecha
{
public:
int anho; // El anho con cuatro cifras, ej. 2004
int mes; // El mes, de 1 a 12
int dia; // El dia, de 1 a 31
void metodoMaravilloso1();
void metodoMaravilloso2();
};
• Ya hemos hecho la clase. Ahora hacemos el resto del código y en unos varios
miles de líneas de código usamos directamente cosas como esta.
Fecha unaFecha;
unaFecha.anho = 2004;
unaFecha.mes = 1;
unaFecha.dia = 25;
class Fecha
{
public:
/* Comentado por ineficiente
int anho;
int mes;
int dia; */
long numeroSegundos;
void metodoMaravilloso1();
void metodoMaravilloso2();
};
• Ya está hecho lo fácil. Ahora sólo hay que ir por las tropecientas mil líneas de
código cambiando nuestras asignaciones y lecturas a los tres enteros anteriores
por el nuevo long.
• Hubiera sido mucho mejor si hubieramos hecho estos tres enteros protegidos y
unos métodos para acceder a ellos. Algo como esto
class Fecha
{
public:
void tomaFecha (int anho, int mes, int dia);
int dameAnho ();
int dameMes ();
int dameDia ();
void metodoMaravilloso1();
void metodoMaravilloso2();
protected:
int anho; // El anho con cuatro cifras, ej. 2004
int mes; // El mes, de 1 a 12
int dia; // El dia, de 1 a 31
};
• Si ahora tenemos que hacer el mismo cambio, basta con cambiar los atributos
protegidos. Los métodos tomaXXX() y dameXXX() se mantienen en cuanto a
parámetros y valor devuelto, pero se modifica su código interno para que
conviertan el año,mes y dia en un long de segundos y al revés. El resto del código
no hay que tocarlo en absoluto.
• Es incluso mejor hacer los atributos privados que protegidos. Haciéndolos
protegidos, las clases hijas (las que heredan de Fecha) pueden acceder
directamente a estos atributos. Cuando hagamos el cambio por un long, debemos
cambiar también el código de las clases hijas. Si los atributos son privados y
obligamos a las clases hijas a acceder a ellos a través de métodos, tampoco
tendremos que cambiar el código de estas clases hijas.
• El acceso a través de métodos es menos eficiente que hacerlo directamente,
así que aunque siguiendo el principio de ocultación es mejor hacer atributos
privados, por eficiencia en algunos casos quizás sea mejor hacerlos protegidos (o
incluso públicos) a riesgo de tener que cambiar más líneas de código en caso de
cambio.
CONSEJO:
Siempre que sea posible hacer los atributos
de una
clase privados.
CONSEJO:
Siempre que sea posible, poner los #define,
definición de tipos,
constantes globales, etc, dentro del fichero
.cc
class InterfaceFecha
{
public:
virtual void tomaFecha (int anho, int mes, int dia) = 0;
virtual int dameAnho () = 0;
virtual int dameMes () = 0;
virtual int dameDia () = 0;
virtual void metodoMaravilloso1() = 0;
virtual void metodoMaravilloso2() = 0;
};
•
De momento, ni siquiera existiría un InterfaceFecha.cc
• La clase Fecha sigue igual, pero hereda de InterfaceFecha.
#include <InterfaceFecha.h>
• Ahora, todo el que necesite una Fecha, tiene que tener un puntero a
InterfaceFecha en vez de a Fecha. Alguien instanciará Fecha y lo guardará en ese
puntero. Es decir, podríamos hacer algo como esto
#include <Fecha.h>
#include <InterfaceFecha.>
...
InterfaceFecha *unaFecha = NULL;
...
unaFecha = new Fecha();
unaFecha->tomaFecha (2004, 1, 27);
...
delete unaFecha;
unaFecha = NULL;
class InterfaceFecha
{
public:
#include <InterfaceFecha.h>
#include <Fecha.h>
InterfaceFecha *InterfaceFecha::dameNuevaFecha()
{
return new Fecha();
}
#include <InterfaceFecha.>
...
InterfaceFecha *unaFecha = NULL;
...
unaFecha = InterfaceFecha::dameNuevaFecha();
unaFecha->tomaFecha (2004, 1, 27);
...
delete unaFecha;
unaFecha = NULL;
• Como vemos, sólo es necesario el #include de InterfaceFecha.h y este no
incluye a Fecha.h (lo hace InterfaceFecha.cc, no el .h). Hemos hecho que este
código no vea en absoluto a Fecha.h. Ahora podemos tocar sin ningún miramiento
el fichero Fecha.h, que este código no necesita ser recompilado.
• Una ventaja adicional es que se puede cambiar la clase Fecha por otra clase
Fecha2 en tiempo de ejecución. Bastaría con poner un atributo estático en
InterfaceFecha para indicar qué clase Fecha queremos y hacer que el método
dameNuevaFecha() instancie y devuelve una u otra en función de ese atributo.
CONSEJO:
Utilizar interfaces para aquellas clases que
preveamos que
pueden cambiar durante el desarrollo del
proyecto o
que creamos que podemos cambiar más
adelante por otra.
Este mecanismo de obtener una instancia de una clase a través de un método estático y
de una interface, para no depender de la clase concreta, creo que dentro del mundo de
los patrones de diseño es el patrón Factoria.
y direccionalidad:
Composición
Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras
clases. La implementación de un método consta de dos partes, una declaración y un
cuerpo. La declaración en Java de un método se puede expresar esquemáticamente
como:
La lista de argumentos es opcional, tanto en Java como en C++, y en los dos casos
puede limitarse a su mínima expresión consistente en dos paréntesis, sin parámetro
alguno en su interior. Opcionalmente, C++ permite utilizar la palabra void para indicar que
la lista de argumentos está vacía, en Java no se usa. Los parámetros, o argumentos, se
utilizan para pasar información al cuerpo del método.
static, indica que los métodos pueden ser accedidos sin necesidad de instanciar un objeto
del tipo que determina la clase. C++ y Java son similares en el soporte de esta
característica.
abstract, indica que el método no está definido en la clase, sino que se encuentra
declarado ahí para ser definido en una subclase (sobreescrito). C++ también soporta esta
capacidad con una sintaxis diferente a Java, pero con similar interpretación.
native, son métodos escritos es otro lenguaje. Java soporta actualmente C y C++.
Los métodos y funciones en C++ pueden devolver una variable u objeto, bien sea por
valor (se devuelve una copia), por puntero o por referencia. Java no soporta punteros, así
que no puede devolver nada por puntero. Todos los tipos primitivos en Java se devuelven
por valor y todos los objetos se devuelven por referencia. El retorno de la referencia a un
objeto en Java es similar a devolver un puntero a un objeto situado en memoria dinámica
en C++, excepto que la sintaxis es mucho más simple en Java, en donde el item que se
devuelve es la dirección de la posición en memoria dinámica donde se encuentra
almacenado el objeto.
Para devolver un valor se utiliza la palabra clave return. La palabra clave return va
seguida de una expresión que será evaluada para saber el valor de retorno. Esta
expresión puede ser compleja o puede ser simplemente el nombre de un objeto, una
variable de tipo primitivo o una constante.
Tanto en Java como en C++ el tipo del valor de retorno debe coincidir con el tipo de
retorno que se ha indicado en la declaración del método; aunque en Java, el tipo actual
de retorno puede ser una subclase del tipo que se ha indicado en la declaración del
método, lo cual no se permite en C++. En Java esto es posible porque todas las clases
heredan desde un objeto raíz común a todos ellos: Object.
El nombre del método puede ser cualquier identificador legal en Java. Java soporta el
concepto de sobrecarga de métodos, es decir, permite que dos métodos compartan el
mismo nombre pero con diferente lista de argumentos, de forma que el compilador pueda
diferenciar claramente cuando se invoca a uno o a otro, en función de los parámetros que
se utilicen en la llamada al método.
El siguiente fragmento de código muestra una clase Java con cuatro métodos
sobrecargados, el último no es legal porque tiene el mismo nombre y lista de argumentos
que otro previamente declarado:
class MiClase {
...
void miMetodo( int x,int y ) { . . . }
void miMetodo( int x ) { . . . }
void miMetodo( int x,float y ) { . . . }
// void miMetodo( int a,float b ) { . . . } // no válido
}
En C++, dos versiones sobrecargadas de una misma función pueden devolver tipos
diferentes. En Java, los métodos sobrecargados siempre deben devolver el mismo tipo.
Métodos de Instancia
Cuando se incluye un método en una definición de una clase Java sin utilizar la palabra
clave static, estamos generando un método de instancia. Aunque cada objeto de la clase
no contiene su propia copia de un método de instancia (no existen múltiples copias del
método en memoria), el resultado final es como si fuese así, como si cada objeto
dispusiese de su propia copia del método.
La llamada a los métodos de instancia en Java se realiza utilizando el nombre del objeto,
el operador punto y el nombre del método.
miObjeto.miMetodoDeInstancia();
En C++, se puede acceder de este mismo modo o utilizando una variable puntero que
apunte al objeto
miPunteroAlObjeto->miMetodoDeInstancia();
Los métodos de instancia tienen acceso tanto a las variables de instancia como a las
variables de clase, tanto en Java como en C++.
Métodos Estáticos
Cuando una función es incluida en una definición de clase C++, o un método e incluso en
una definición de una clase Java, y se utiliza la palabra static, se obtiene un método
estático o método de clase.
Lo más significativo de los métodos de clase es que pueden ser invocados sin necesidad
de que haya que instanciar ningún objeto de la clase. En Java se puede invocar un
método de clase utilizando el nombre de la clase, el operador punto y el nombre del
método.
MiClase.miMetodoDeClase();
En C++, hay que utilizar el operador de resolución de ámbito para poder invocar a un
método de clase:
MiClase::miMetodoDeClase();
En Java, los métodos de clase operan solamente como variables de clase; no tienen
acceso a variables de instancia de la clase, a no ser que se cree un nuevo objeto y se
acceda a las variables de instancia a través de ese objeto.
Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma
página de variables; es decir, todos los objetos que se generen comparten la misma zona
de memoria. Los métodos estáticos se usan para acceder solamente a variables
estáticas.
class UnaClase {
int var;
UnaClase() {
var = 5;
}
unMetodo() {
var += 5;
}
}
Paso de parámetros
En C++, se puede declarar un método en una clase y definirlo luego dentro de la clase
(bajo ciertas condiciones) o definirlo fuera de la clase. A la hora de declararlo, es
necesario indicar el tipo de argumentos que necesita, pero no se requiere indicar sus
nombres (aunque pueda hacerse). A la hora de definir el método sí tiene que indicarse el
nombre de los argumentos que necesita el método.
En Java, todos los métodos deben estar declarados y definidos dentro de la clase, y hay
que indicar el tipo y nombre de los argumentos o parámetros que acepta. Los argumentos
son como variables locales declaradas en el cuerpo del método que están inicializadas al
valor que se pasa como parámetro en la invocación del método.
En Java, todos los argumentos de tipos primitivos deben pasarse por valor, mientras que
los objetos deben pasarse por referencia. Cuando se pasa un objeto por referencia, se
está pasando la dirección de memoria en la que se encuentra almacenado el objeto.
Si se modifica una variable que haya sido pasada por valor, no se modificará la variable
original que se haya utilizado para invocar al método, mientras que si se modifica una
variable pasada por referencia, la variable original del método de llamada se verá
afectada de los cambios que se produzcan en el método al que se le ha pasado como
argumento.
// Clase principal
class java515 {
En C++, se puede pasar como parámetro un puntero que apunte a una función dentro de
otra función, y utilizar este puntero en la segunda función para llamar a la primera. Esta
capacidad no está directamente soportada en Java. Sin embargo, en algunos casos, se
puede conseguir casi lo mismo encapsulando la primero función como un método de
instancia de un objeto y luego pasar el objeto a otro método, donde el primer método se
puede ejecutar a través del objeto.
Tanto en Java como en C++, los métodos tienen acceso directo a las variables miembro
de la clase. El nombre de un argumento puede tener el mismo nombre que una variable
miembro de la clase. En este caso, la variable local que resulta del argumento del
método, oculta a la variable miembro de la clase.
Cuando se instancia un método se pasa siempre una referencia al propio objeto que ha
llamado al método, es la referencia this.
public: indica que es un método accesible a través de una instancia del objeto.
Sin especificar: indica visibilidad de paquete, se puede acceder a través de una instancia,
pero sólo de clases que se encuentren en el mismo paquete.
Un tipo básico.
Un objeto de una clase o interfaz. En este tipo de objetos se incluyen las matrices o
vectores.
Métodos
Estructuración en Java
Los métodos pueden ser invocados o llamados de cualquier método de la clase, incluido
él mismo.
Además, cuando se invoca, hay que pasar un valor a cada paramétro, a través de una
variable o un valor constante. En Java, la acción de pasar valores a parámetros de tipo
primitivo (int, double, boolean, char..) se denomina paso de parámetros por valor
En éste caso, los argumentos que se pasan, no pueden ser modificados por la función.
En caso que el parámetro sea de tipo Clase o arreglo, lo que se está haciendo es un
paso de parámetros por referencia, y en este caso, los parámetros si pueden ser
modificados por el método
Cuerpo
Ejemplo
Si se coloca las palabras public static antes del método se logra un comportamiento de
tipo global.
import java.io.*;
class suma
{
public static void main(String arg[ ]) throws IOException
{
int x,y;
int s = suma(x,y);
}
Bajar archivo
Salida a pantalla
class metodo1
{
public static void main(String arg[ ])
{
int a = 5;
if ( par(a) == true)
{
System.out.println(a + " es par ");
}
else
{
System.out.println(a + " es impar");
}
}
return p;
}
}
Ejemplo
Código fuente
import java.io.*;
class palindromes
{
public static void main(String Arg[ ]) throws IOException
{
int numero = 0;
int contador = 0;
numero++;
}
}
num_inv = 0;
div_entera = num;
resto = 0;
while (div_entera != 0)
{
resto = div_entera % 10;
div_entera = div_entera / 10;
num_inv = num_inv * 10 + resto;
}
return num_inv;
}
}
Bajar archivo
Ejercicios
**nota : un numero perfecto es aquel que la suma de sus divisores sea igual
al numero
ej --> 6 : 1 + 2 + 3
Para ello el usuario, debe ingresar por teclado dos números enteros (a y b) e
implementar dos funciones :
par(num) : boolean
perfecto(num) : boolean
ALGORITMO
---------
FIN
INICIO
cont <-- 0
leer a , b
FIN
RETORNAR sum
FIN
INICIO
leer a , b
s <-- sumatoria(a,b)
escribir "El valor de la sumatoria de " + a + " hasta " + b + " es : " + s
FIN
9. EXPLIQUE AMPLIAMENTE, PARA ESCRIBIR CORRECTAMENTE LAS
EXPRESIONES QUE SE UTLIZAN PARA COMUNICARSE A TRAVES DE LOS
METODOS
float precio;
Contador laCuenta;
Sólo que aquí no se declaran private, public, etc., sino que las variables definidas dentro
del método sólo son accesibles por él.
Asignaciones a variables
laCuenta.cnt = 0;
Operaciones matemáticas
Por ejemplo: -cnt; // cambia de signo; por ejemplo si cnt es 12 el resultado es -12; cnt no
cambia.
++cnt; // equivale a cnt += 1;
Binarios: + - * / % …..etc.
Multiplicativos * / %
Aditivos + -
Igualdad == !=
OR lógico ||
Condicional ? :
Algunos ejemplos:
<= devuelve "true" si un valor es menor o igual que otro: total <= maximo;
Llamadas a métodos
Se llama a un método de la misma clase simplemente con el nombre del método y los
parámetros entre paréntesis, como se ve, entre otros, en el ejemplo en negrita:
// Archivo: Complejo.java
// Compilar con: javac Complejo.java
public final class Complejo extends Number {
// atributos:
private float x;
private float y;
// constructor:
public Complejo(float rx, float iy) {
x = rx;
y = iy;
}
// métodos:
public float Norma() {
return (float)Math.sqrt(x*x+y*y);
}
// obligatorios (son abstractos en Number):
public double doubleValue() {
return (double)Norma( );
}
public float floatValue() {
return Norma();
}
public int intValue() {
return (int)Norma();
}
public long longValue() {
return (long)Norma();
}
public String toString() {
return "("+x+")+i("+y+")";
}
}
Pueden probar la clase (mínima) con el siguiente ejemplo de aplicación; la línea en
negrita es un ejemplo de un llamado a un método de un objeto de otra clase. Notar que es
este caso, es necesario llamar al método sobre un objeto (instancia) existente, por lo que
se indica:
Nombre_del_Objeto<punto>Nombre_del_Método(parámetros)
// Archivo: Ejemplo4.java
// Compilar con: javac Ejemplo4.java
// Ejecutar con: java Ejemplo4
import java.io.*;
return (float)Math.sqrt(x*x+y*y);
Como el método es de clase, no hace falta llamarlo para un objeto en particular. En ese
caso, en lugar del nombre de un objeto existente se puede utilizar directamente el nombre
de la clase:
Nombre_de_la_Clase<punto>Nombre_del_Método(parámetros)
Práctica
return edad;
edad = laEdad;
return nombre;
nombre = elNombre;
listaParámetros es la lista de los parámetros que tomará la función separados por comas
y definidos cada uno de ellos como:
tipo nombreParámetro
synchronized: Es un método que sólo puede ser ejecutado por un hilo, y hasta que ese
hilo no acabe la llamada al método, no puede comenzar la llamada al método otro hilo. Lo
emplearemos al trabajar con hilos.
La cláusula opcional throws es empleada para indicar que dentro del método se pueden
generar errores en su ejecución, y que debemos estar preparados para tratarlos.
El método posee un par de llaves, dentro de las cuales estará el código que se ejecutará
al ser llamada la función. Dicho código estará formado por instrucciones válidas en el
lenguaje, finalizadas generalmente por punto y coma.
1.- Cuando se usan variables como parametros, la variable que se manda debe ser
declarada dentro del principal o del procedimiento de donde se esta enviando. 2.- La
variable que se manda tiene un nombre, la que se recibe puede tener otro nombre. 3.- La
cantidad de variables que se envian deben ser igual en cantidad, orden y tipo a las
variables que reciben. 4.- La variable que recibe tiene un ambito local dentro del
procedimiento, es decir solo la puede usar ese procedimiento. Y se pueden mandar datos,
valores (excepto decimales), expresiones algebraicas, pero siempre se recibe en
variables.
Los parametros de una función son los valores que esta recibe por parte del código que la
llama. Pueden ser tipos simples u objetos.
Está función recibe dos parámetros, ambos de tipo entero, uno el divisor y otro el
dividendo.
Es importante recordar que en java, los parametros de los tipos primitivos (int, long, etc.)
SIEMPRE se pasan por valor. Los objetos se pasan por referencia.
En el paso de parámetros a funciones hay dos aproximaciones clásicas: el paso por valor
y paso por referencia.
En el paso por valor se realiza una copia de los valores que se pasan, trabajando dentro
de la función con la copia. Es por ello que cualquier cambio que sufran dentro, no
repercute fuera de la función.
En el paso por referencia no se realiza dicha copia, por lo que las modificaciones de
dentro de las funciones afectan a los parámetros y esos cambios permanecen al final de
la función.
En Java el paso por parámetro es por valor, aunque los efectos son de paso por
referencia cuando los argumentos son objetos. ¿cómo sucede eso? Pues es muy fácil, si
una función tiene como argumento un tipo primitivo (int, float, etc...), en Java se realiza
una copia para la función y cualquier cambio a dicho argumento no afecta a la variable
original. Este paso de parámetros en Java está orientado a utilizar el valor de la variable
para otros cálculos.
En el caso de los objetos es distinto. En realidad lo que sucede es que en Java siempre
tenemos referencias a los objetos. Por eso al pasar a una función como argumento un
objeto, pasamos la referencia al mismo, es decir, aunque se hace una copia para el paso
por valor, como lo que se copia es una referencia, los cambios al objeto referenciado sí
son visibles y afectan fuera de la función.
La única excepción es la clase String , cuyos objetos no son mutables. Cualquier
modificación de un objeto String lleva aparejada la creación de una nueva instancia del
objeto. Si deseamos el mismo comportamiento para el paso de parámetros del resto de
objetos, tenemos que recurrir al objeto StringBuffer.
Existen ocasiones en que es necesario mandar al procedimiento ciertos valores para que
los use en algún proceso.
Estos valores que se pasan del cuerpo principal del programa al procedimiento se llaman
parametros.
{ cuerpo de instrucciones; };
prog13.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
PrintWriter pagina;
HttpServletResponse response)
pagina =response.getWriter();
response.setContentType("text/html");
pagina.println("<HTML>");
double beta=3.1416;
proc1(8+4 , beta, "juan" );
pagina.println("</HTML>");
pagina.close();
};
double c = a + b;
};
1.- Cuando se usan variables como parametros, la variable que se manda debe ser
declarada dentro del principal o del procedimiento de donde se esta enviando.
2.- La variable que se manda tiene un nombre, la que se recibe puede tener otro nombre.
3.- La cantidad de variables que se envian deben ser igual en cantidad, orden y tipo a las
variables que reciben.
4.- La variable que recibe tiene un ambito local dentro del procedimiento, es decir solo la
puede usar ese procedimiento.
Construir una tabla de multiplicar que el usuario indique captura y control de ciclo en el
principal, calculo en un procedimiento.
Existe comúnmente la creencia errónea de que en Java es posible pasar parámetros por
referencia, y no es así. Java siempre pasa los parámetros por valor. Esta confusión se
da debido a que todas las variables de objeto son referencias a objetos [1]. En el libro
“The Java Programming Language” de Ken Arnold y James Gosling (autores de Java),
sección 2.6.1., tenemos la siguiente cita: “There is exactly one parameter passing mode in
Java - pass by value - and that helps keep things simple.” [2] (Existe un solo modo de
paso de parámetros en Java – paso por valor – y eso ayuda a mantener las cosas
simples.).
Antes de continuar, vamos a recordar cuáles son las definiciones de paso por valor y paso
por referencia: [3]:
Paso por valor significa que cuando un argumento se pasa a una función, la función
recibe una copia del valor original. Por lo tanto, si la función modifica el parámetro, sólo la
copia cambia y el valor original permanece intacto.
Paso por referencia significa que cuando un argumento se pasa a una función, la
función recibe la dirección de memoria del valor original, no la copia del valor. Por lo tanto,
si la función modifica el parámetro, el valor original en el código que llamó a la función
cambia.
Vamos a valernos de ejemplos para explicar el mecanismo con el que Java pasa
parámetros a los métodos.
Remitámonos a línea 20. Declaramos una variable pass, de tipo ValorOReferencia, con
su único atributo param1 inicializado con el valor “Objeto inicial.”. En la línea 23,
presentamos el objeto en pantalla, y se muestra el valor con el que fue declarado.
Suponiendo que el paso de parámetros en Java fuera por referencia, la referencia pass
apuntaría ahora a un nuevo objeto con el valor “Este es un nuevo objeto.”. Pero, al
regresar al método main, en la línea 25, presentamos de nuevo pass, y vemos que el
valor con el que fue originalmente declarado se mantiene.
Ahora, vamos a pasar pass y vamos a modificar solamente su único atributo. En la línea
28, pasamos pass al método cambiarParam1, en donde tenemos la sentencia
en la línea 16. Así, se ha modificado el valor del atributo param1, y al volver al método
main, presentamos pass otra vez:
Al ver esta última operación, quizá alguien pueda decir que Java sí pasa parámetros por
referencia, ya que se modificó el atributo del objeto, pero estaría equivocado: ¿Por qué en
cambiarObjeto la variable pass no sufre ninguna modificación, y en el método
cambiarParam1 su atributo se ve efectivamente modificado? Porque Java no pasa
objetos como parámetros [4], sino copias de las referencias a esos objetos. Exacto.
Java pasa parámetros por valor. Pasa referencias a objetos por valor.
cambiarObjeto(ValorOReferencia objeto)
objeto, que originalmente era una copia de la referencia pass, apunta ahora a un nuevo
objeto creado en otra posición de memoria. Es por eso que de vuelta al main, el objeto
apuntado por pass no ha cambiado.
cambiarParam1(ValorOReferencia objeto)
, lo que estamos haciendo es invocar al método setParam1 del objeto apuntado por la
referencia objeto. Es por eso que el atributo param1 del objeto referenciado por pass es
efectivamente modificado.
Hay que tener claro entonces, que cuando se escribe una sentencia del estilo
cualquierFuncion(CualquierTipo argumento);
Posee el mismo nombre de la clase a la cual pertenece y no puede regresar ningún valor
(ni siquiera se puede especificar la palabra reservada void). Por ejemplo si añadiéramos a
la clase SSuma un constructor, tendríamos que llamarlo también SSuma. Cuando en una
clase no se escribe propiamente un constructor, Java asume uno por default.
Un constructor por default es un constructor sin parámetros que no hace nada. Sin
embargo será invocado cada vez que se construya un objeto sin especificar ningún
argumento, en cuyo caso el objeto será iniciado con los valores predeterminados por el
sistema (los atributos numéricos a ceros, los alfanuméricos a nulos, y las referencias a
objetos a null).
public SSuma() {}
class Arychan
{
//ATRIBUTOS
private String nombre;
private String descripción;
private String formaDeSer;
private double salario;
//METODOS
//...
El operador new crea un nuevo objeto, en este caso de la clase Arychan, y a continuación
se invoca al constructor de la clase para realizar las operaciones de iniciación que estén
programadas. Ahora invocaremos al constructor con parámetros, recordemos que la clase
Arychan es una persona con características como divertida, hermosa, mismas que
pasaremos como argumentos.
public Arboles() {
System.out.println("Un árbol genérico");
}
Clase Arboles
• Como en todo programa Java , primeramente se define la Clase a través del
vocablo class.
• Dentro del método principal (main) son generadas cuatro instancias de la Clase,
como se puede observar, al ser generada la instancia a través del vocablo new se
pasa un parámetro, y es dependiendo de este parámetro que es llamado el
Constructor correspondiente, el cual a su vez invoca la Clase System.out.println
que imprime a pantalla.
Constructor Obligatorio...
En los ejemplos anteriores del curso se pudo notar que no se hizo uso de Constructor
alguno, y la razón es que el compilador lleva acabo esta definición de Constructor
vacío detrás de los escenarios, sin embargo, existe una situación en la que es
necesario definir un Constructor vacío y esta es cuando se hace uso de otros
constructores.
Tanto Java como C++ soportan la sobrecarga de métodos, es decir, que dos o más
métodos puedan tener el mismo nombre, pero distinta lista de argumentos en su
invocación. Si se sobrecarga un método, el compilador determinará ya en tiempo de
compilación, en base a lista de argumentos con que se llame al método, cual es la versión
del método que debe utilizar.
Cuando se declara una clase en Java, se pueden declarar uno o más constructores
opcionales que realizan la inicialización cuando se instancia (se crea una ocurrencia) un
objeto de dicha clase.
MiClase mc;
mc = new MiClase();
La palabra clave new se usa para crear una instancia de la clase. Antes de ser
instanciada con new no consume memoria, simplemente es una declaración de tipo.
Después de ser instanciado un nuevo objeto mc, el valor de i en el objeto mc será igual a
10. Se puede referenciar la variable (de instancia) i con el nombre del objeto:
Al tener mc todas las variables y métodos de MiClase, se puede usar la primera sintaxis
para llamar al método Suma_a_i() utilizando el nuevo nombre de clase mc:
mc.Suma_a_i( 10 );
Luego, en Java, cuando se instancia un objeto, siempre se hace una llamada directa al
constructor como argumento del operador new. Este operador se encarga de que el
sistema proporcione memoria para contener al objeto que se va a crear.
En C++, los objetos pueden instanciarse de diferentes formas, pero en Java solamente se
pueden instanciar en la pila de memoria, es decir, solamente se pueden instanciar
utilizando el operador new para poder solicitar memoria al sistema en tiempo de ejecución
y utilizar el constructor para instanciar el objeto en esa zona de memoria. Si al intentar
instanciar un objeto, la Máquina Virtual Java (JVM) no puede localizar la memoria que
necesita ese objeto, bien inmediatamente o haciendo ejecutarse al reciclador de memoria,
el sistema lanzará un OutOfMemoryError.
MiClase objeto;
Las dos sentencias siguientes muestran cómo se utiliza el constructor en Java para
declarar, instanciar y, opcionalmente, inicializar un objeto:
Las dos sentencias devuelven una referencia al nuevo objeto que es almacenada en la
variable miObjeto. También se puede invocar al constructor sin asignar la referencia a una
variable. Esto es útil cuando un método requiere un objeto de un tipo determinado como
argumento, ya que se puede incluir una llamada al constructor de este objeto en la
llamada al método:
miMetodo( new MiConstructor( 1,2,3 ) );
Tanto en Java como en C++, cuando un método o una función comienza su ejecución,
todos los parámetros se crean como variables locales automáticas. En este caso, el
objeto es instanciado en conjunción con la llamada a la función que será utilizada para
inicializar esas variables locales cuando comience la ejecución y luego serán guardadas.
Como son automáticas, cuando el método concluye su ejecución, se destruirá (en C++) o
será marcado para su destrucción (en Java).
class MiClase {
int varInstancia;
void verVarInstancia() {
System.out.println( "El Objeto contiene " + varInstancia );
}
}
class java507 {
public static void main( String args[] ) {
System.out.println( "Lanzando la aplicacion" );
// Instanciamos un objeto de este tipo llamando al
// constructor de defecto
java507 obj = new java507();
// Llamamos a la funcion pasandole un constructor
// parametrizado como parametro
obj.miFuncion( new MiClase( 100 ) );
}
super( parametros_opcionales );
Esto hará que se ejecute el constructor de la superclase, utilizando los parámetros que se
pasen para la inicialización. En el código del ejemplo siguiente, java508.java, se ilustra el
uso de esta palabra clase para llamar al constructor de la superclase desde una subclase.
class SuperClase {
int varInstancia;
void verVarInstancia() {
System.out.println( "El Objeto contiene " + varInstancia );
}
}
class java508 {
public static void main( String args[] ) {
System.out.println( "Lanzando la aplicacion" );
// Instanciamos un objeto de este tipo llamando al
// constructor de defecto
java508 obj = new java508();
// Llamamos a la funcion pasandole un constructor de la
// subclase parametrizado como parametro
obj.miFuncion( new SubClase( 100 ) );
}
Control de Acceso
private
Ninguna otra clase puede instanciar objetos de la clase. La clase puede contener
métodos públicos, y estos métodos pueden construir un objeto y devolverlo, pero nadie
más puede hacerlo.
protected
public
package
Nadie desde fuera del paquete puede construir una instancia de la clase. Esto es útil si se
quiere tener acceso a las clases del paquete para crear instancias de la clase, pero que
nadie más pueda hacerlo, con lo cual se restringe quien puede crear instancias de la
clase.
En Java y en C++, una instancia de una clase, un objeto, contiene todas las variables y
métodos de instancia de la clase y de todas sus superclases. Sin embargo, los dos
lenguajes soportan la posibilidad de sobreescribir un método declarado en una
superclase, indicando el mismo nombre y misma lista de argumentos; aunque los
procedimientos para llevar a cabo esto son totalmente diferentes en Java y en C++.
Como aclaración a terminología que se empleo en este documento, quiero indicar que
cuando digo sobrecargar métodos, quiero decir que Java requiere que los dos métodos
tengan el mismo nombre, devuelvan el mismo tipo, pero tienen una diferente lista de
argumentos. Y cuando digo sobreescribir métodos, quiero decir que Java requiere que los
dos métodos tengan el mismo nombre, mismo tipo de retorno y misma lista de
argumentos de llamada.
En Java, si una clase define un método con el mismo nombre, mismo tipo de retorno y
misma lista de argumentos que un método de una superclase, el nuevo método
sobreescribirá al método de la superclase, utilizándose en todos los objetos que se creen
en donde se vea involucrado el tipo de la subclase que sobreescribe el método.
Finalizadores
Java no utiliza destructores (al contrario que C++) ya que tiene una forma de recoger
automáticamente todos los objetos que se salen del alcance. No obstante proporciona un
método que, cuando se especifique en el código de la clase, el reciclador de memoria
(garbage collector) llamará:
La regla de oro a seguir es que no se debe poner ningún código que deba ser ejecutado
en el método finalize(). Por ejemplo, si se necesita concluir la comunicación con un
servidor cuando ya no se va a usar un objeto, no debe ponerse el código de desconexión
en el método finalize(), porque puede que nunca se llamado. Luego, en Java, es
responsabilidad del programador escribir métodos para realizar limpieza que no involucre
a la memoria ocupada por el objeto y ejecutarlos en el instante preciso. El método
finalize() y el reciclador de memoria son útiles para liberar la memoria de la pila y debería
restringirse su uso solamente a eso, y no depender de ellos para realizar ningún otro tipo
de limpieza.
No obstante, Java dispone de dos métodos para asegurar que los finalizadores se
ejecuten. Los dos métodos habilitan la finalización a la salida de la aplicación, haciendo
que los finalizadores de todos los objetos que tengan finalizador y que todavía no hayan
sido invocados automáticamente, se ejecuten antes de que la Máquina Virtual Java
concluya la ejecución de la aplicación. Estos dos métodos son:
Una clase también hereda de sus superclase el método finalize(), y en caso necesario,
debe llamarse una vez que el método finalize() de la clase haya realizado las tareas que
se le hayan encomendado, de la forma:
super.finalize();
Noción de constructor
Cuando se crea un objeto (se instancia una clase) es posible definir un proceso de
inicialización que prepare el objeto para ser usado. Esta inicialización se lleva a cabo
invocando un método especial denominado constructor. Esta invocación es implícita y se
realiza automáticamente cuando se utiliza el operador new. Los constructores tienen
algunas características especiales:
Continuando con los ejemplos del capítulo anterior se podría escribir un constructor para
la clase Punto, de la siguiente forma:
class Punto {
int x , y ;
Punto ( int a , int b ) {
x=a;y=b;
}
}
Si una clase no declara ningún constructor, Java incorpora un constructor por defecto
(denominado constructor no-args) que no recibe ningún argumento y no hace nada.
class Punto {
int x , y ;
Punto ( ) { }
}
Sobrecarga de constructores.
Una clase puede definir varios constructores (un objeto puede inicializarse de varias
formas). Para cada instanciación se usa el que coincide en número y tipo de argumentos.
Si no hay ninguno coincidente se produce un error en tiempo de compilación.
Por ejemplo:
class Punto {
int x , y ;
Punto ( int a , int b ) {
x=a;y=b;
}
Punto () {
x = 0 ; y = 0;
}
}
class Punto {
int x , y ;
Punto ( int a , int b ) {
x=a;y=b;
}
Punto () {
this (0,0);
}
}
Cuando se declaran varios constructores para una misma clase estos deben distinguirse
en la lista de argumentos, bien en el número, bien en el tipo.
Constructores
Clases:
En lenguaje C tradicional existen las estructuras de datos, las cuales se definen con la
palabra clave struct, ejemplo:
struct Coordenadas
{
int x;
int y;
int z;
}
Con una estructura uno crea un tipo de dato nuevo, en este caso, se puede declarar una
variable de tipo Coordenadas, la cual puede almacenar 3 valores enteros:
x, y, z son los "datos miembros" de la estructura. Para manipular estos datos, (asignarles
un valor inicial, cargarlos, mostrarlos, etc.), uno puede escribir funciones globales en su
programa. Ejemplo:
void Carga(void)
void Muestra(void)
Bueno, se podría decir que una estructura es el "antepasado" más directo de una clase.
¿Por qué?.
Que tal si las funciones con las cuales uno manipula los datos de la estructura formaran
parte de ella, o sea, una estructura tal que además de definir sus datos miembros también
definiera las funciones para manipularlos. Este tipo de estructuras existe en C++ y se
definen igual que las estructuras de C pero además uno puede declarar las funciones.
Mire el siguiente ejemplo: (para estos ejemplos puede usar Visual C++ o Borland C++ 3.1,
con cualquiera de ellos funcionan).
struct Coordenadas
{
int x,y,z;
void main(void)
{
struct Coordenadas coo; //Se define una variable, (coo), de tipo Coordenadas.
class Coordenadas
{
int x,y,z;
public:
void Cargar(void)
{
x=8;
y=9;
z=10;
}
void Mostrar(void)
{
cout << x <<endl;
cout << y <<endl;
cout << z <<endl;
}
};
void main(void)
{
Coordenadas coo;
coo.Cargar();
coo.Mostrar();
(*) En la POO, utilizando clases, ya no se habla de "definir" una variable de una clase en
particular, sino que se crea una "instancia" o un objeto de dicha clase.
A veces la diferencia, aparte de la sintaxis, no es del todo "pesada" como para justificar
una clase. En este ejemplo no hacía falta definir una clase, la versión de la estructura es
más que suficiente.
Pero cuando el concepto del objeto a crear es un tanto más complejo, y preocupa, por
ejemplo, la protección de los contenidos de los datos miembros, o se tiene una gran
cantidad de funciones miembros, o simplemente se pretende en serio programar según
POO, es cuando una clase se hace presente.
Pues como supongo astutamente dedujo, la Programación Orientada a Objetos, consta
de objetos, y una clase, define o es como la "plantilla" sobre la cual se construyen los tan
mentados.
Constructores:
En una clase existe una función miembro muy particular llamada Constructor.
Un constructor es una función que debe tener el mismo nombre que la clase y no debe
retornar ningún valor, (ni siquiera void), y se encarga de asignarle valores iniciales, (o
simplemente inicializar), a los datos miembros.
En el ejemplo descubrirá que allí no hay ningún constructor definido, cuando ocurre esto
el compilador de C++ crea en ejecución el constructor.
No obstante hubiera sido correcto haber definido un constructor que se encargara de, por
ejemplo, inicializar con 0 los datos miembros.
Un constructor es invocado automáticamente cuando se crea la instancia, o sea que no
hay llamarlo explícitamente desde el programa principal.
Existen 3 tipos de constructores:
El constructor por defecto es, en caso que no lo haya definido, el que C++ en tiempo de
ejecución le asigne, o bien:
class Coordenadas
{
int x,y,z;
public:
Coordenadas(); //Constructor por defecto
};
También le podríamos haber agregado a este constructor, encerrados entre llaves, los
valores iniciales para los datos:
{x=0;y=0;z=0;}.
Cuando se crea el objeto se escribe:
void main(void)
{
Coordenadas coo;
....
}
El constructor común es aquel que recibe parámetros para asignarles como valores
iniciales a los datos miembros, o sea que al crear la instancia, se pasó unos parámetros
para inicializar.
class Coordenadas
{
int x,y,z;
public:
Coordenadas(int p, int q, int t) {x=p; y=q; z=t;} //Constructor común.
};
void main(void)
{
Coordenadas coo(6,7,22); //Se le pasa los valores para inicializar.
.....
}
El constructor de copia se utilizan para inicializar un objeto con otro objeto de la misma
clase.
class Coordenadas
{
int x,y,z;
public:
Coordenadas ( int p, int q, int t) {x=p; y=q; z=t;} //Constructor común.
Coordenadas(const Coordenadas c) //Constructor de copia.
{
x=c.x;
y=c.y;
z=c.z;
}
};
void main(void)
{
Coordenadas k(1,2,3); //Creación de un objeto con lo valores iniciales 1, 2 y
3.
Coordenadas coo=k; //Se llama al constructor de copia para que le asigne a coo
los valores de k.
....
}
Los constructores son funciones, ¿¿¿cómo permite el compilador dos funciones con el
mismo nombre???.
Ahh, buena pregunta.
El compilador de C++ permitiría 100 funciones con el mismo nombre, el único requisito es
que cada una de ellas tenga diferente número y/o tipo de parámetros.
Esta cualidad, que no se aplica solamente a los constructores y funciones miembros de
una clase, sino que a cualquier función de un programa de C++, se llama Sobrecarga de
funciones o Polimorfismo.
Cuando se llama a la función, C++ selecciona de todas las funciones sobrecargadas
aquella que se ajusta de acuerdo con los parámetros pasados, en cantidad y tipo.
Funciones InLine:
También se puede estar preguntando, si las funciones miembros de una clase pueden
estar definidas fuera de la clase.
La respuesta es sí, por lo general las funciones miembros están definidas fuera de la
clase, dentro de esta última sólo se declararían los prototipos.
En el caso que la función esté definida dentro de la clase, ésta se llama función inline,
como las funciones Cargar() y Mostrar() de nuestra clase Coordenadas. Se podría incluso
agregar la cláusula inline, pero no hace falta.
¿Qué diferencia hay entre una función inline y otra, (definida dentro o fuera de la clase)?
Se define una función inline cuando es muy corta y simple, como los constructores y esas
funciones del ejemplo. Declarar una función en línea significa que el compilador puede, si
así lo decide, reemplazar cada invocación por la función, con la frecuencia que sea, por el
código encerrado entre llaves.
Hay que tener en cuenta que funciones inline extensas consumen más memoria, a pesar
que elimina el tiempo que lleva hacer la invocación.
Cuando se escribe una función fuera de la clase se especifica el acceso de la siguiente
forma:
Así quedaría nuestro programa, con la clase con un constructor por defecto y con las
funciones miembro fuera de la clase.
#include <iostream.h>
class Coordenadas
{
int x,y,z;
public:
Coordenadas(){x=0;y=0;z=0;} //Constructor por defecto.
void Cargar(void); //Prototipo de las funciones.
void Mostrar(void);
};
void main(void)
{
Coordenadas coo;
coo.Cargar();
coo.Mostrar();
}
Destructores:
Existe una función especial más para las clases, y se trata de los destructores.
Un destructor es una función miembro que se llama cuando se destruye la clase.
Todas las clases tiene un destructor implícito, incluso aunque no esté declarado. El
destructor implícito no hace nada en particular, pero si uno quiere, puede declarar un
destructor de forma explícita. Su sintaxis sería:
class NombreClase
{
...
public:
~NombreClase();
...
}
El destructor debe comenzar con el caracter "ñuflo", (~), seguido por el nombre de la
clase, (igual que el constructor). Además el destructor no puede recibir parámetros ni
retornar nada, (ni siquiera void).
No puede haber más de un destructor para una clase y si no se define uno
explícitamente, el compilador crea uno automáticamente.
El destructor se llama automáticamente siempre que una variable de ese tipo de clase,
(una instancia u objeto), sale fuera de su ámbito, (por ejemplo cuando termina el
programa).
Especificadores de acceso:
Ya había dicho que por defecto los datos miembros de una clase son privados. ¿Qué
significa esto?.
Que sólo las funciones miembros públicas de la misma clase tienen acceso a ellos. Si lo
desea puede escribir la cláusula private al momento de declarar los datos.
En cambio la cláusula public es obligatoria cuando se desea declarar un dato público y
este dato estará disponible para cualquier función del programa.
Existe una cláusula más, protected. Los datos definidos a continuación de esta cláusula
están restringidos para cualquier función externa a la clase, pero son públicos para la
propia clase y los miembros de clases derivadas.
Ya se ha dicho que una clase es únicamente una especificación. Para poder utilizar la
funcionalidad contenida en la misma, se deben instanciar las clases.
Un objeto se puede instanciar de una forma simple, declarando una variable del tipo
de la clase.
En Ppal.h:
#include "ObjGraf.h"
En Ppal.cpp:
//--------------------------------------------------
//--------------------------------------------------
Aunque esta forma es posible, y bastante utilizada en la programación de C++ clásica,
en C++ Builder se utiliza en muy contadas ocasiones. Esto es así por dos razones,
fundamentalmente:
La duración de los objetos suele ir más allá de una simple función o bloque. Debido al
enfoque de la programación dirigida por eventos, suele ser habitual que un objeto se
cree en un gestor de eventos y se destruya en otro.
Creación Dinámica
Cuando usamos new para instanciar un objeto, se usa una variable que referencie o
apunte al nuevo objeto creado (de otra manera éste quedaría totalmente
inaccesible). En definitiva, se requiere la declaración previa de un puntero a objetos
del tipo del que se va a crear.
En Ppal.cpp:
//--------------------------------------------------
//--------------------------------------------------
La forma de establecer el estado inicial o destruir las componentes de un objeto se
estudiarán en el apartado dedicado a Constructores y Destructores (sección 5.4).
Destrucción de objetos
Cuando un objeto deja de ser útil hay que eliminarlo. De esta manera la aplicación
recupera los recursos (memoria) que ese objeto había acaparado cuando se creó.
En Ppal.cpp:
//--------------------------------------------------
Salida: Muere mi parte Perro … Muere mi parte Mamífero … Muere mi parte Animal …
El destructor es muy similar al constructor, excepto que es llamado automáticamente
cuando cada objeto sale de su ámbito de validez. Recordemos que las variables
automáticas tienen un tiempo de vida limitado, ya que dejan de existir cuando se sale del
bloque en que han sido declaradas. Cuando un objeto es liberado automáticamente, su
destructor, si existe, es llamado automáticamente.
Un destructor tiene el mismo nombre que la clase a la que pertenece, pero precedido con
una tilde (~). Un destructor no tiene tipo devuelto.
En el ejemplo anterior, podemos definir un destructor que asigne cero a las variables
antes de que sean liberadas, con lo que en realidad no estamos haciendo nada:
# include <iostream.h>
class Caja {
double longitud, anchura, altura;
public:
Caja (double dim1, double dim2, double dim3);
~Caja (void);
double volumen (void);
};
main ()
{
Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);
cout << "El volumen de la caja grande es " << grande.volumen() << '\n';
}
#include <iostream.h>
class Taco {
public:
Taco (int hard) {
hardness = new int;
*hardness = hard;
}
~Taco() {
cout << "Destroying taco with hardness " ;
cout << *hardness <<;\n';
delete hardness;
}
private:
int *hardness; };
main ()
{
Taco hard(10);
Taco *soft = new Taco (0);
delete soft;
};
En este ejemplo, vemos que el constructor tiene el mismo nombre que la clase, con un ~
delante. Cuando se crean punteros a clases, como soft en el ejemplo, se llama al
destructor cuando se libera la memoria del puntero. Si esto no se hace, nunca se llamará
al destructor.
Con clases declaradas estáticamente, como Taco hard, el destructor se llama al final de la
función donde se declara el objeto (en el ejemplo, al final de la función main.
Incluse cuando se interrumpe un programa usando una llamada a exit(), se llama a los
destructores de los objetos que existen en ese momento.
Destructores
void __destruct ( void )
Ejemplo de Destructor
<?php
class MyDestructableClass {
function __construct() {
print "In constructor\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "Destroying " . $this->name . "\n";
}
}
Nota: El destructor es llamado durante la finalización del script, de tal manera que los
headers ya han sido enviados.
Nota: Intentar arrojar una excepción desde un destructor produce un error fatal.
Ejemplo
En el siguiente ejemplo se crean tres clases que forman una cadena de herencia. La
clase First es la clase base, Second se deriva de First y Third se deriva de Second. Las
tres tienen destructores. En Main(), se crea una instancia de la clase más derivada.
Cuando ejecute el programa, observe que se llama a los destructores de las tres clases
automáticamente y en orden, desde la más derivada hasta la menos derivada.
class First
~First()
}
class Second: First
~Second()
~Third()
class TestDestructors
Resultados
Third's destructor is called
class TestDestructors
{
static void Main()
{
Third t = new Third();
}
}
Resultados
Third's destructor is called
El intérprete de Java posee un sistema de recogida de basura, que por lo general permite
que no nos preocupemos de liberar la memoria asignada explícitamente.
El recolector de basura será el encargado de liberar una zona de memoria dinámica que
había sido reservada mediante el operador new, cuando el objeto ya no va a ser utilizado
más durante el programa (por ejemplo, sale del ámbito de utilización, o no es referenciado
nuevamente).
A veces una clase mantiene un recurso que no es de Java como un descriptor de archivo
o un tipo de letra del sistema de ventanas. En este caso sería acertado el utilizar la
finalización explícita, para asegurar que dicho recurso se libera. Esto se hace mediante la
destrucción personalizada, un sistema similar a los destructores de C++.
Debe observarse que el método finalize () es de tipo protected void y por lo tanto deberá
de sobreescribirse con este mismo tipo.
Un destructor es un método que pertenece a una clase y el mismo (en C++) debe tener el
mismo nombre de la clase a la que pertenece. A diferencia de los otros métodos de la
clase, un destructor deberá ser del tipo void, es decir, el mismo no regresará valor
alguno. Para diferenciar a un método destructor de un método constructor, al nombre del
destructor se le debe anteponer el caracter ~ (Alt + 126).
Los destructores suelen usarse para liberar memoria que haya sido solicitada por el
objeto a travez de las ordenes malloc(), new, etc. En tales casos se deberá incluir dentro
del método destructor la orden free, delete, etc., según sea el caso.
public:
// constructor de base ( nulo )
Pareja() {}
// constructror parametrizado
Pareja(double x, double y) : a(x), b(y) {}
// destructor
~Pareja() {}
// métodos
double getA();
double getB();
void setA(double n);
void setB(double n);
};