Java 02
Java 02
Java 02
Una clase es por tanto una pantilla implementada en software que describe un conjunto
de objetos con atributos y comportamiento similares.
Una instancia u objeto de una clase es una representación concreta y específica de una
clase y que reside en la memoria del ordenador.
Atributos
Los atributos son las caracterísiticas individuales que diferencian un objeto de otro y
determinan su apariencia, estado u otras cualidades. Los atributos se guardan en
variables denominadas de instancia, y cada objeto particular puede tener valores
distintos para estas variables.
Además de las variables de instancia hay variables de clase, las cuales se aplican a la
clase y a todas sus instancias. Por ejemplo, el número de ruedas de un automóvil es el
mismo cuatro, para todos los automóviles.
Comportamiento
El proyecto
La clase
Para crear una clase se utiliza la palabra reservada class y a continuación el nombre de
la clase. La definición de la clase se pone entre las llaves de apertura y cierre. El nombre
de la clase empieza por letra mayúscula.
class Rectangulo{
//miembros dato
//funciones miembro
}
Los valores de los atributos se guardan en los miembros dato o variables de instancia.
Los nombres de dichas variables comienzan por letra minúscula.
Vamos a crear una clase denominada Rectangulo, que describa las características
comunes a estas figuras planas que son las siguientes:
class Rectangulo{
int x;
int y;
int ancho;
int alto;
//faltan las funciones miembro
}
El nombre de las funciones miembro o métodos comieza por letra minúscula y deben
sugerir acciones (mover, calcular, etc.). La definición de una función tiene el siguiente
formato:
Entre las llaves de apertura y cierre se coloca la definición de la función. tipo indica el
tipo de dato que puede ser predefinido int, double, etc, o definido por el usuario, una
clase cualquiera.
Cuando se llama a la función, los argumentos arg1, arg2, arg3 se copian en los
parámetros parm1, parm2, parm3 y se ejecutan las sentencias dentro de la función. La
función finaliza cuando se llega al final de su bloque de definición o cuando encuentra
una sentencia return.
Cuando una función no devuelve nada se dice de tipo void. Para llamar a la función, se
escribe
Estudiaremos más adelante con más detalle como se definen las funciones.
Una función suele finalizar cuando llega al final del bloque de su definición
void funcion(....){
//sentencias...
}
void funcion(....){
//sentencias...
if(condicion) return;
//sentencias..
}
Una función puede devolver un valor (un tipo de dato primitivo o un objeto).
double funcion(....){
double suma=0.0;
//sentencias...
return suma;
}
Cualquier variable declarada dentro de la función tiene una vida temporal, existiendo en
memoria, mientras la función esté activa. Se trata de variables locales a la función. Por
ejemplo:
La variable parm, existe desde el comienzo hasta el final de la función. La variable local
i, existe desde el punto de su declaración hasta el final del bloque de la función.
Se ha de tener en cuenta que las funciones miembro tienen acceso a los miembros dato,
por tanto, es importante en el diseño de una clase decidir qué variables son miembros
dato, qué variables son locales a las funciones miembro, y qué valores les pasamos a
dichas funciones. Los ejemplos nos ayudarán a entender esta distinción.
Hemos definido los atributos o miembros dato de la clase Rectangulo, ahora le vamos
añadir un comportamiento: los objetos de la clase Rectangulo o rectángulos sabrán
calcular su área, tendrán capacidad para trasladarse a otro punto del plano, sabrán si
contienen en su interior un punto determinado del plano.
La función que calcula el área realizará la siguiente tarea, calculará el producto del
ancho por el alto del rectángulo y devolverá el resultado. La función devuelve un entero
es por tanto, de tipo int. No es necasario pasarle datos ya que tiene acceso a los
miembros dato ancho y alto que guardan la anchura y la altura de un rectángulo
concreto.
class Rectangulo{
int x;
int y;
int ancho;
int alto;
int calcularArea(){
return (ancho*alto);
}
}
class Rectangulo{
int x;
int y;
int ancho;
int alto;
void desplazar(int dx, int dy){
x+=dx;
y+=dy;
}
}
Como se tienen que cumplir las cuatro condiciones a la vez, se unen mediante el
operador lógico AND simbolizado por &&.
class Rectangulo{
int x;
int y;
int ancho;
int alto;
boolean estaDentro(int x1, int y1){
if((x1>x)&&(x1<x+ancho)&&(y1>y)&&(y1<y+ancho)){
return true;
}
return false;
}
}
Los contructores
Un objeto de una clase se crea llamando a una función especial denominada constructor
de la clase. El constructor se llama de forma automática cuando se crea un objeto, para
situarlo en memoria e inicializar los miembros dato declarados en la clase. El
constructor tiene el mismo nombre que la clase. Lo específico del constructor es que no
tiene tipo de retorno.
class Rectangulo{
int x;
int y;
int ancho;
int alto;
Rectangulo(int x1, int y1, int w, int h){
x=x1;
y=y1;
ancho=w;
alto=h;
}
}
El constructor recibe cuatro números que guardan los parámetros x1, y1, w y h, y con
ellos inicializa los miembros dato x, y, ancho y alto.
Una clase puede tener más de un constructor. Por ejemplo, el siguiente constructor crea
un rectángulo cuyo origen está en el punto (0, 0).
class Rectangulo{
int x;
int y;
int ancho;
int alto;
Rectangulo(int w, int h){
x=0;
y=0;
ancho=w;
alto=h;
}
}
Este constructor crea un rectángulo de dimensiones nulas situado en el punto (0, 0),
class Rectangulo{
int x;
int y;
int ancho;
int alto;
Rectangulo(){
x=0;
y=0;
ancho=0;
alto=0;
}
}
Los objetos
Por ejemplo,
new reserva espacio en memoria para los miembros dato y devuelve una referencia que
se guarda en la variable rect1 del tipo Rectangulo que denominamos ahora objeto.
Dicha sentencia, crea un objeto denominado rect1 de la clase Rectangulo llamando al
segundo constructor en el listado. El rectángulo estará situado en el punto de
coordenadas x=10, y=20; tendrá una anchura de ancho=40 y una altura de alto=80.
Por ejemplo, podemos acceder al miembro dato ancho, para cambiar la anchura de un
objeto rectángulo.
rect1.ancho=100;
El rectángulo rect1 que tenía inicialmente una anchura de 40, mediante esta sentencia se
la cambiamos a 100.
Desde un objeto llamamos a las funciones miembro para realizar una determinada tarea.
Por ejemplo, desde el rectángulo rect1 llamamos a la función calcularArea para calcular
el área de dicho rectángulo.
rect1.calcularArea();
La función miembro area devuelve un entero, que guardaremos en una variable entera
medidaArea, para luego usar este dato.
int medidaArea=rect1.calcularArea();
System.out.println("El área del rectángulo es "+medidaArea);
rect2.desplazar(10, 20);
Podemos verificar mediante el siguiente código si el punto (20, 30) está en el interior
del rectángulo rect1.
if(rect1.estaDentro(20,30)){
System.out.println("El punto está dentro del
rectángulo");
}else{
System.out.println("El punto está fuera del
rectángulo");
}
rect1.dentro() devuelve true si el punto (20, 30) que se le pasa a dicha función miembro
está en el interior del rectángulo rect1, ejecutándose la primera sentencia, en caso
contrario se ejecuta la segunda.
if(rect1.estaDentro(20,30)){
System.out.println("El punto está dentro del
rectángulo");
}else{
System.out.println("El punto está fuera del
rectángulo");
}
}
}
La vida de un objeto
En el lenguaje C++, los objetos que se crean con new se han de eliminar con delete.
new reserva espacio en memoria para el objeto y delete libera dicha memoria. En el
lenguaje Java no es necesario liberar la memoria reservada, el recolector de basura
(garbage collector) se encarga de hacerlo por nosotros, liberando al programador de una
de las tareas que más quebraderos de cabeza le producen, olvidarse de liberar la
memoria reservada.
Veamos un ejemplo
Si escribimos
rect3=rect1;
La destrucción de un objeto es una tarea (thread) de baja prioridad que lleva a cabo la
Máquina Virtual Java (JVM). Por tanto, nunca podemos saber cuando se va a destruir
un objeto.
Puede haber situaciones en las que es necesario realizar ciertas operaciones que no
puede realizar el recolector de basura (garbage collector) cuando se destruye un objeto.
Por ejemplo, se han abierto varios archivos durante la vida de un objeto, y se desea que
los archivos estén cerrados cuando dicho objeto desaparece. Se puede definir en la clase
un método denominado finalize que realice esta tarea. Este método es llamado por el
recolector de basura inmeditamente antes de que el objeto sea destruído.
Identificadores
Cómo se escriben los nombres de la variables, de las clases, de las funciones, etc., es un
asunto muy importante de cara a la comprensión y el mantenimiento de código. En la
introducción a los fundamentos del lenguaje Java hemos tratado ya de los
identificadores.
El código debe de ser tanto más fácil de leer y de entender como sea posible. Alguien
que lea el código, incluso después de cierto tiempo, debe ser capaz de entender lo que
hace a primera vista, aunque los detalles internos, es decir, cómo lo hace, precise un
estudio detallado.
Vemos primero un ejemplo que muestra un código poco legible y por tanto, muy difícil
de mantener
Cuen(int ba){
this.ba=ba;
}
public void dep(int i){
ba+=i;
}
public boolean ret(int i){
if(ba>=i){
ba-=i;
return true;
}
return false;
}
public int get(){
return ba;
}
}
Este es una programa sencillo de una cuenta bancaria. El tipo de dato puede ser entero
(int o long), si la unidad monetaria tiene poco valor como la peseta, o un número
decimal (double) si la unidad monetaria es de gran valor como el Euro y el Dólar.
2.2 Composición
Hay dos formas de reutilizar el código, mediante la composición y mediante la herencia.
La composición significa utilizar objetos dentro de otros objetos. Por ejemplo, un applet
es un objeto que contiene en su interior otros objetos como botones, etiquetas, etc. Cada
uno de los controles está descrito por una clase.
Vamos a estudiar una nueva aproximación a la clase Rectangulo definiendo el origen,
no como un par de coordenadas x e y (números enteros) sino como objetos de una nueva
clase denominada Punto.
La clase Punto
La clase Punto tiene dos miembros dato, la abscisa x y la ordenada y de un punto del
plano. Definimos dos constructores uno por defecto que sitúa el punto en el origen, y
otro constructor explícito que proporciona las coordenadas x e y de un punto concreto.
Cuando el nombre de los parámetros es el mismo que el nombre de los miembros datos
escribimos
this.x que está a la izquierda y que recibe el dato x que se le pasa al constructor se
refiere al miembro dato, mientras que x que está a la derecha es el parámetro. this es
una palabra reservada que guarda una refrencia al objeto propio, u objeto actual.
Tendremos ocasión a lo largo del curso de encontrar esta palabra en distintas
situaciones.
La función miembro desplazar simplemente cambia la posición del punto desde (x, y) a
(x+dx, y+dy). La función desplazar cuando es llamada recibe en sus dos parámetros dx
y dy el desplazamiento del punto y actualiza las coordenadas x e y del punto. La función
no retorna ningún valor
p.desplazar(-10, 40);
La clase Rectangulo
La clase Rectangulo tiene como miembros dato, el origen que es un objeto de la clase
Punto y las dimensiones ancho y alto.
El constructor por defecto, crea un rectángulo situado en el punto 0,0 y con dimensiones
nulas
public Rectangulo() {
origen = new Punto(0, 0);
ancho=0;
alto=0;
}
public Rectangulo(Punto p) {
this(p, 0, 0);
}
public Rectangulo(int w, int h) {
this(new Punto(0, 0), w, h);
}
public Rectangulo() {
origen = new Punto(0, 0);
ancho=0;
alto=0;
}
public Rectangulo(Punto p) {
this(p, 0, 0);
}
public Rectangulo(int w, int h) {
this(new Punto(0, 0), w, h);
}
public Rectangulo(Punto p, int w, int h) {
origen = p;
ancho = w;
alto = h;
}
void desplazar(int dx, int dy) {
origen.desplazar(dx, dy);
}
int calcularArea() {
return ancho * alto;
}
}
Objetos de la clase Rectangulo
Para crear un rectángulo rect1 situado en el punto (0, 0) y cuyas dimensiones son 100 y
200 escribimos
Para desplazar el rectángulo rect1 desde el punto (100, 200) a otro punto situado 40
unidades hacia la derecha y 20 hacia abajo, sin modificar sus dimensiones, escribimos
rect1.desplazar(40, 20);
tipo_de_dato[] nombre_del_array;
int[] numeros;
numeros=new int[4];
numeros[0]=2;
numeros[1]=-4;
numeros[2]=15;
numeros[3]=-25;
No necesitamos recordar el número de elementos del array, su miembro dato length nos
proporciona la dimensión del array. Escribimos de forma equivalente
Los arrays se pueden declarar, crear e inicializar en una misma línea, del siguiente modo
Java verifica que el índice no sea mayor o igual que la dimensión del array, lo que
facilita mucho el trabajo al programador.
• Declarar
Rectangulo[] rectangulos;
• Crear el array
rectangulos=new Rectangulo[3];
• Usar el array
Arrays multidimensionales
Una matriz bidimensional puede tener varias filas, y en cada fila no tiene por qué haber
el mismo número de elementos o columnas. Por ejemplo, podemos declarar e inicializar
la siguiente matriz bidimensional
double[][] matriz={{1,2,3,4},{5,6},{7,8,9,10,11,12},{13}};
Para mostrar los elementos de este array bidimensional escribimos el siguiente código
Mostramos los elementos de una fila separados por un tabulador usando la función
print. Una vez completada una fila se pasa a la siguiente mediante println.
Los arrays bidimensionales nos permiten guardar los elementos de una matriz.
Queremos crear y mostrar una matriz cuadrada unidad de dimensión 4. Recordaremos
que una matriz unidad es aquella cuyos elementos son ceros excepto los de la diagonal
principal i==j, que son unos. Mediante un doble bucle for recorremos los elementos de
la matriz especificando su fila i y su columna j. En el siguiente programa
buscado:
for(i=0; i<matriz.length; i++){
for(j=0; j<matriz[i].length; j++){
if(matriz[i][j]==numero){
break buscado;
}
}
}
System.out.println("buscado: matriz("+ i+",
"+j+")="+matriz[i][j]);
La primera sentencia que encontramos en el código fuente de las distintas clases que
forman el proyecto es package o del nombre del paquete.
//archivo MiApp.java
package nombrePaquete;
public class MiApp{
//miembros dato
//funciones miembro
}
//archivo MiClase.java
package nombrePaquete;
public class MiClase{
//miembros dato
//funciones miembro
}
Para importar clases de un paquete se usa el comando import. Se puede importar una
clase individual
import java.awt.Font;
import java.awt.*;
Para crear un objeto fuente de la clase Font podemos seguir dos alternativas
import java.awt.Font;
Font fuente=new Font("Monospaced", Font.BOLD, 36);
import java.awt.*;
public class BarTexto extends Panel implements java.io.Serializable{
//...
}
Panel es una clase que está en el paquete java.awt, y Serializable es un interface que
está en el paquete java.io
Paquete Descripción
Contiene las clases necesarias para crear applets que se ejecutan
java.applet
en la ventana del navegador
Contiene clases para crear una aplicación GUI independiente de
java.awt
la plataforma
java.io Entrada/Salida. Clases que definen distintos flujos de datos
Contiene clases esenciales, se importa impícitamente sin
java.lang
necesidad de una sentencia import.
Se usa en combinación con las clases del paquete java.io para
java.net
leer y escribir datos en la red.
java.util Contiene otras clases útiles que ayudan al programador
El constructor
public Lista(int[] x) {
this.x=x;
n=x.length;
}
Como apreciamos basta una simple asignación para inicializar el miembro dato x que es
un array de enteros, con el array de enteros x que se le pasa al constructor. Por otra
parte, cuando se le pasa a una función un array se le pasa implícitamente la dimensión
del array, que se puede obtener a partir de su miembro dato length.
Las funciones miembro tienen acceso a los miembros dato, el array de enteros x y la
dimensión del array n.
• El valor medio
Para hallar el valor medio, se suman todos los elementos del array y se divide el
resultado por el número de elementos.
double valorMedio(){
int suma=0;
for(int i=0; i<n; i++){
suma+=x[i];
}
return (double)suma/n;
}
Para codificar esta función se ha de tener algunas precauciones. La suma de todos los
elementos del array se guarda en la variable local suma. Dicha variable local ha de ser
inicializada a cero, ya que una variable local contrariamente a lo que sucede a los
miembros dato o variables de instancia es inicializada con cualquier valor en el
momento en que es declarada.
• El valor mayor
int valorMayor(){
int mayor=x[0];
for(int i=1; i<n; i++){
if(x[i]>mayor) mayor=x[i];
}
return mayor;
}
Se compara cada elemento del array con el valor de la variable local mayor, que
inicialmente tiene el valor del primer elemento del array, si un elemento del array es
mayor que dicha variable auxiliar se guarda en ella el valor de dicho elemento del array.
Finalmente, se devuelve el valor mayor calculado
• El valor menor
int valorMenor(){
int menor=x[0];
for(int i=1; i<n; i++){
if(x[i]<menor) menor=x[i];
}
return menor;
}
En el proceso de ordenación se ha de
intercambiar los valores que guardan elementos
del array. Veamos como sería el código
correspondiente al intercambio de los valores
que guardan dos variables x e y.
aux=x;
x=y;
y=aux;
Para ordenar una lista de números emplearemos el
método de la burbuja, un método tan simple como
poco eficaz. Se compara el primer elemento, índice
0, con todos los demás elementos de la lista, si el
primer elemento es mayor que el elemento j, se
intercambian sus valores, siguiendo el
procedimiento explicado en la figura anterior. Se
continua este procedimiento con todos los
elementos del array menos el último. La figura
explica de forma gráfica este procedimiento.
void ordenar(){
int aux;
for(int i=0; i<n-1; i++){
for(int j=i+1; j<n; j++){
if(x[i]>x[j]){
aux=x[j];
x[j]=x[i];
x[i]=aux;
}
}
}
}
Caben ahora algunas mejoras en el programa, así la función ordenar la podemos utilizar
para hallar el valor mayor, y el valor menor. Si tenemos una lista ordenada en orden
ascendente, el último elemento de la lista será el valor mayor y el primero, el valor
menor. De este modo, podemos usar una función en otra funciones, lo que resulta en un
ahorro de código, y en un aumento de la legibilidad del programa.
int valorMayor(){
ordenar();
return x[n-1];
}
int valorMenor(){
ordenar();
return x[0];
}
void imprimir(){
ordenar();
for(int i=0; i<n; i++){
System.out.print("\t"+x[i]);
}
System.out.println("");
}
Para calcular el área de un círculo, creamos un objeto circulo de la clase Circulo dando
un valor al radio. Desde este objeto llamamos a la función miembro calcularArea.
Veamos ahora las ventajas que supone declarar la constante PI como miembro estático.
Si PI y radio fuesen variables de instancia
Al crearse cada objeto se reservaría espacio para el dato radio (64 bits), y para el dato PI
(otros 64 bits). Véase la sección tipos de datos primitivos. Como vemos en la parte
superior de la figura, se desperdicia la memoria del ordenador, guardando tres veces el
mismo dato PI.
Miembros estáticos
Las variables de clase o miembros estáticos son aquellos a los que se antepone el
modificador static. Vamos a comprobar que un miembro dato estático guarda el mismo
valor en todos los objetos de dicha clase.
Sea una clase denominada Alumno con dos miembros dato, la nota de selectividad, y un
miembro estático denominado nota de corte. La nota es un atributo que tiene un valor
distinto para cada uno de los alumnos u objetos de la clase Alumno, mientras que la nota
de corte es un atributo que tiene el mismo valor para a un conjunto de alumnos. Se
define también en dicha clase una función miembro que determine si está (true) o no
(false) admitido.
Creamos ahora un array de cuatro alumnos y asignamos a cada uno de ellos una nota.
int numAdmitidos=0;
for(int i=0; i<alumnos.length; i++){
if (alumnos[i].estaAdmitido()){
numAdmitidos++;
}
}
System.out.println("admitidos "+numAdmitidos);
alumnos[1].notaCorte=7.0;
Comprobamos que todos los objetos de la clase Alumno tienen dicho miembro dato
estático notaCorte cambiado a 7.0
El miembro dato notaCorte tiene el modificador static y por tanto está ligado a la clase
más que a cada uno de los objetos de dicha clase. Se puede acceder a dicho miembro
con la siguiente sintaxis
Nombre_de_la_clase.miembro_estático
Si ponemos
Alumno.notaCorte=6.5;
for(int i=0; i<alumnos.length; i++){
System.out.println("nota de corte "+alumnos[i].notaCorte);
}
Veremos que todos los objetos de la clase Alumno habrán cambiado el valor del
miembro dato estático notaCorte a 6.5.
Un miembro dato estático de una clase se puede acceder desde un objeto de la clase, o
mejor, desde la clase misma.
2.7 Una clase con funciones estáticas
El propósito de este capítulo es definir un conjunto de funciones estáticas dentro de una
clase denominada Matematicas. Al definir el concepto de clase estudiamos las
funciones miembro o métodos. En esta página, vamos a ver cómo se define una función
para realizar una determinada tarea.
Se deberá tener en cuenta que no se puede usar una variable de instancia por una
función estática. Por ejemplo
class UnaClase{
int i=0;
static void funcion(){
System.out.println(i);
}
//otras funciones miembro
}
Podemos simplificar el código sin necesidad de declarar una variable temporal abs.
O bien,
Una función similar nos hallará el valor absoluto de un dato de tipo double.
Para hallar la potencia de un número se multiplica tantas veces la base como indica el
exponente. Por ejemplo, para hallar la quinta potencia de 3, se escribirá
35=3*3*3*3*3
Podemos realizar este producto en un bucle for, de manera que en la variable resultado
se vaya acumulando el resultado de los sucesivos productos, tal como se recoge en la
Tabla, entre paréntesis figura el valor de resultado en la iteración previa, el valor inicial
es 1.
long resultado=1;
for(int i=0; i<5; i++)
resultado*=3;
long resultado=1;
for(int i=0; i<exponente; i++)
resultado*=base;
Por último, le pondremos una etiqueta a esta tarea, asociaremos esta rutina al nombre de
una función.
El factorial de un número
long resultado=1;
while(numero>0){
resultado*=numero;
numero--;
}
Es mucho más lógico definir una función que calcule el factorial del número num,
que repetir tres veces el código del cálculo del factorial para hallar el número
combinatorio m sobre n, de acuerdo a la siguiente fórmula.
long num=factorial(m);
long den=factorial(n)*factorial(m-n);
boolean bPrimo=true;
for(int i=3; i<numero/2; i+=2){
if(numero%i==0){
bPrimo=false;
break;
}
}
Ahora, se trata de poner las líneas de código dentro de una función que determine si un
número es primo (devuelve true) o no es primo (devuelve false), para que pueda ser
reutilizada. El código de la función esPrimo es la siguiente
La clase Matematicas
Ahora solamente nos queda poner todas las funciones en el interior de una clase que le
llamaremos Matematicas, anteponiéndole a cada una de las funciones la palabra
reservada static.
Nombre_de_la_clase.función_miembro(...);
double y=-2.5;
System.out.print("El valor absoluto de "+y);
System.out.println(" vale "+Matematicas.absoluto(y));