A lo largo de la historia de la informática, han ido apareciendo diferentes paradigmas
de programación. En primer lugar, apareció la programación secuencial, que
consistía en secuencias de sentencias que se ejecutaban una tras otra. El lenguaje
ensamblador o el lenguaje COBOL son lenguajes secuenciales. Entonces no existía
el concepto de función, que apareció más adelante en los lenguajes procedimentales,
como el BASIC o el C. La evolución no terminó aquí, y continuó hasta
el paradigma más extendido en la actualidad: la programación orientada a objetos.
Smalltalk, C++ o Java pertenecen a esta nueva generación.
Cada nuevo paradigma ha extendido el anterior, de manera que podemos encontrar
las características de un lenguaje secuencial en uno procedimental, y
las de uno procedimental en uno orientado a objetos. De hecho, hasta ahora
sólo hemos visto la vertiente procedimental del lenguaje Java.
Estudiaremos qué es una clase y qué es un objeto, cómo se definen y cómo se utilizan. Además, conoceremos los aspectos más relevantes de la programación orientada a objetos y las principales diferencias respecto a la programación procedimental.
2.1. Clases y objetos
En la programación orientada a objetos, aparecen por primera vez los conceptos
de clase y objeto. Una clase es como una especie de patrón conceptual, mientras
que un objeto es la materialización de dicho patrón. Imaginemos la clase “motocicleta”.
Todos estamos de acuerdo en que todas las motocicletas tienen características
comunes que nos permiten distinguirlas como tales. Una motocicleta,
entre otras cosas, tiene dos ruedas, carece de techo y tiene un manillar y un motor
de una determinada cilindrada. Además, será de alguna marca y tendrá algún
nombre de modelo. A este esquema mental, a este patrón, lo llamaremos “clase”.
Sin embargo, motocicletas hay muchas: la de mi hermano, la del vecino, la del
concesionario de enfrente..., todas de diferentes marcas, cilindradas y colores. A
esta materialización del patrón le daremos el nombre de “objeto”. Clase sólo hay
una, pero objetos puede haber muchos.
Las líneas siguientes declaran la clase “Producto” y sus atributos:
class Producto {
int código;
String descripción;
double precio;
}
Como se puede observar, el nombre de la clase va precedido por la palabra reservada
class (clase). Los atributos se declaran del mismo modo que las variables,
aunque encerrados entre llaves.
Cabe recordar que una clase es siempre una simplificación de la realidad. Por
esta razón, nunca declararemos atributos para todas y cada una de las características
del ente que queramos representar; sólo crearemos aquellos que necesitemos
para resolver el problema que se nos plantea.
Por otro lado, podemos tener una o más funciones que lean y escriban esos
atributos, por ejemplo, una función que se encargue de leer y otra que se encargue
de cambiar (escribir) el precio del producto. Estas funciones, que están
estrechamente ligadas a los atributos y que no tienen razón de ser sin ellos, se
declaran en el ámbito de la clase.
El código siguiente:
public class Producto {
int código;
String descripción;
double precio;
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
}
int código;
String descripción;
double precio;
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
}
Declara la clase “Producto” con dos funciones miembro: “fijarPrecio” y “obtenerPrecio”.
Es importante observar que ambas funciones tienen visibilidad sobre los
atributos de la clase, es decir, pueden acceder a ellos de la misma forma que acceden
a sus propios parámetros y variables.
2.2. Instanciación de objetos
Como ya hemos comentado, una clase es sólo un patrón de objetos. Los
atributos de una clase no existen en la memoria del ordenador hasta que
no se materializan en un objeto. A este proceso de materialización se le llama
instanciación.
Un objeto se instancia mediante el operador new (nuevo).
El código siguiente:
Producto sal;
sal =
new Producto();
instancia el objeto “sal” de la clase “Producto”. Como se puede observar, “sal”
debe declararse como una variable del tipo “Producto”. Los paréntesis después
del nombre de la clase son obligatorios.
A partir de la instanciación, ya tenemos en memoria un objeto con todos sus
atributos. Cada nueva instanciación crea un objeto nuevo, independiente de
los demás. Si tenemos, por ejemplo, dos objetos, “sal” y “azúcar”:
Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
en la memoria tendremos dos pares de atributos “código”, “descripción” y
“precio”, uno por cada objeto:
Para acceder a estos atributos, utilizaremos el operador “.” (punto).
Las líneas siguientes:
Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.precio = 0.60;
// fija el precio del paquete de azúcar
azúcar.precio = 0.81
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.precio = 0.60;
// fija el precio del paquete de azúcar
azúcar.precio = 0.81
inicializan el atributo “precio” de los objetos “sal” y “azúcar” a 0,60 y 0,81 respectivamente.
Observad la independencia entre ambos objetos. El operador
“.” nos permite especificar de forma unívoca a qué atributo de qué objeto hacemos
referencia.
De la misma forma (usando el operador “.”) accederemos a las funciones
miembro de la clase. El código siguiente hace exactamente lo mismo que el
anterior, pero usando la función “fijarPrecio” que anteriormente habíamos
definido:
Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.fijarPrecio(0.60);
// fija el precio del paquete de azúcar
azúcar.fijarPrecio(0.81);
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.fijarPrecio(0.60);
// fija el precio del paquete de azúcar
azúcar.fijarPrecio(0.81);
public class Producto {
int código;
String descripción;
double precio;
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
public static void main(String[] args) {
Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.fijarPrecio(0.60);
// fija el precio del paquete de azúcar
azúcar.fijarPrecio(0.81);
}
}
int código;
String descripción;
double precio;
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
public static void main(String[] args) {
Producto sal, azúcar;
sal = new Producto();
azúcar = new Producto();
// fija el precio del paquete de sal
sal.fijarPrecio(0.60);
// fija el precio del paquete de azúcar
azúcar.fijarPrecio(0.81);
}
}
Las funciones miembro de una clase, como “fijarPrecio” u
“obtenerPrecio”, acceden directamente a los atributos de un objeto sin necesidad
de especificar a qué objeto pertenecen. Esto es así porque las funciones miembro
siempre se ejecutan en el contexto de un objeto. Es decir, cuando nos encontramos
con la llamada:
sal.fijarPrecio(0.60);
ejecutamos la función para el objeto “sal”. Entonces, dentro de la función,
cada vez que se hace referencia a un atributo, a éste, implícitamente, se le supone
de la clase “sal”.
2.3. El objeto this
Como ya hemos explicado, cuando una función miembro accede a un atributo lo
hace en el contexto de un objeto. Por eso no es necesario que especifique a qué
objeto hace referencia. Sin embargo, hay casos en los que puede ser interesante
disponer de dicho objeto, por ejemplo para pasarlo por parámetro a otra función
o para distinguirlo de una variable o un parámetro homónimo.
Las funciones miembro pueden acceder al objeto actual mediante el objeto
predefinido this (éste). A continuación, se muestra una implementación alternativa
(y equivalente) de la función “fijarPrecio”:
void fijarPrecio(double precio) {
this.precio = precio;
}
this.precio = precio;
}
En este caso, se utiliza this para distinguir entre el atributo de la clase y el parámetro
de la función. El “this.precio” hace referencia al atributo “precio” del
objeto actual (this), mientras que “precio”, sin el this, hace referencia al pará-
metro de la función.
En este caso, mediante this explicitamos a qué objeto hacemos
referencia. Entonces, si tenemos la llamada:
en el contexto de la función “fijarPrecio”, this será el mismo objeto que “sal”.
2.4. El constructor
Cuando se instancia un objeto, puede ser necesario inicializar sus atributos.
Volviendo al ejemplo anterior, establecíamos el precio del producto después
de haberlo instanciado, ya fuese accediendo directamente al atributo “precio”
o mediante la función “fijarPrecio”.
Sin embargo, las clases nos ofrecen un mecanismo
mucho más elegante de inicializar un objeto: el constructor.
El constructor es una función como cualquier otra, salvo por un par de particularidades:
se llama como la clase y no tiene tipo de retorno.
Las líneas siguientes muestran un posible constructor de la clase “Producto”:
Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
El constructor espera tres parámetros: el código, la descripción
y el precio del producto. En este caso, la tarea que lleva a cabo el constructor
es relativamente sencilla: tan sólo copiar el código, la descripción y el precio
proporcionados a los atributos homónimos.
El ejemplo siguiente instancia otra vez los productos “sal” y “azúcar”. Sin embargo,
esta vez se hace uso del constructor que acabamos de definir:
Producto sal, azúcar;
sal = new Producto(80005355, "Sal", 0.60);
azúcar = new Producto(800053588, "Azúcar", 0.81);
sal = new Producto(80005355, "Sal", 0.60);
azúcar = new Producto(800053588, "Azúcar", 0.81);
Agrupándolo todo, el código quedaría así:
public class Producto {
int código;
String descripción;
double precio;
// el constructor: inicializa el objeto Producto
Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
public static void main(String[] args) {
Producto sal, azúcar;
sal = new Producto(80005355, "Sal", 0.60);
azúcar = new Producto(80005388, "Azúcar", 0.81);
int código;
String descripción;
double precio;
// el constructor: inicializa el objeto Producto
Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
public static void main(String[] args) {
Producto sal, azúcar;
sal = new Producto(80005355, "Sal", 0.60);
azúcar = new Producto(80005388, "Azúcar", 0.81);
System.out.println("Precio de 1 paquete de sal: " +
sal.obtenerPrecio() + " EUR");
System.out.println("Precio de 1 paquete de azúcar: " +
azúcar.obtenerPrecio() + " EUR");
}
}
sal.obtenerPrecio() + " EUR");
System.out.println("Precio de 1 paquete de azúcar: " +
azúcar.obtenerPrecio() + " EUR");
}
}
Lo que sigue al operador new no es otra cosa que la llamada al
constructor de la clase. Después de la instanciación, los atributos de los objetos
“sal” y “azúcar” ya estarán inicializados con los valores que hayamos pasado
al constructor. El programa generará la salida siguiente:
Precio de 1 paquete de sal: 0.60 EUR
Precio de 1 paquete de azúcar: 0.81 EUR
Precio de 1 paquete de azúcar: 0.81 EUR
Una clase puede tener más de un constructor, del mismo modo que puede tener
dos o más funciones con el mismo nombre. Sin embargo, como en el caso
de las funciones, los parámetros deben ser distintos.
Si no se define ningún constructor para una clase, ésta tendrá un construc- a
tor implícito: el constructor por defecto. El constructor por defecto, que no
espera ningún parámetro, es el que usábamos en las primeras implementaciones
de la clase “Producto”, cuando aún no habíamos definido ningún
constructor:
azúcar = new Producto(); // llamada al constructor por defecto
2.5. Extensión y herencia
La programación orientada a objetos (POO de ahora en adelante) establece un
mecanismo fundamental que nos permite definir una clase en términos de
otra: la extensión. La idea subyacente es partir de una clase general para generar
una clase más específica que considere alguna característica o funcionalidad
no cubierta por la clase más general.
Supongamos ahora que en nuestro colmado empezamos a vender productos
a granel. La aproximación anterior, en la que cada producto tenía un
precio, deja de ser válida para todos los productos. Ahora hay productos
que tienen un precio por kilogramo y un peso, además del código y la descripción.
Está claro que, en gran medida, los productos que se venden a
granel no difieren demasiado de los que se venden por unidades.
De hecho,
sólo cambia un poco el significado del concepto de precio, que ahora no es
por unidad sino por kilo. Además, tenemos un nuevo atributo, el peso, y
una nueva forma de computar el precio, el precio (por kilo) multiplicado
por el peso.
Jugando con este símil, el código siguiente define la clase “ProductoGranel”
como una extensión de la clase “Producto”:
class ProductoGranel extends Producto {
// añade el atributo peso (del producto)
double peso;
// el constructor también añade el parámetro peso
ProductoGranel(int código, String descripción, double
precio, double peso) {
super(código, descripción, precio);
this.peso = peso;
}
// para los productos vendidos a granel, el precio es
// igual al resultado de multiplicar el peso (en Kg) por
// el precio por Kg
double obtenerPrecio() {
return precio * peso;
}
}
// añade el atributo peso (del producto)
double peso;
// el constructor también añade el parámetro peso
ProductoGranel(int código, String descripción, double
precio, double peso) {
super(código, descripción, precio);
this.peso = peso;
}
// para los productos vendidos a granel, el precio es
// igual al resultado de multiplicar el peso (en Kg) por
// el precio por Kg
double obtenerPrecio() {
return precio * peso;
}
}
En primer lugar, observad el uso de la palabra clave extends (extiende):
class ProductoGranel extends Producto {
Mediante esta construcción, definimos una clase en términos de otra. Esto implica
que la clase “ProductoGranel” tendrá los mismos atributos y funciones
que la clase “Producto”, más aquellos que defina de forma expresa. A este mecanismo
de transmisión de atributos y funciones se le llama herencia.
Observad también la llamada al constructor de la clase “Producto” desde el
constructor de la clase “ProductoGranel” mediante la palabra reservada super
(superclase):
super(código, descripción, precio);
Como ya hemos comentado, la clase “ProductoGranel” hereda los atributos y
las funciones de la clase “Producto”. Esto le permite llamar al constructor de
la clase “Producto” para inicializar aquellos atributos que ha heredado. La llamada
al constructor de la clase madre, si la hay, debe ser la primera sentencia
del constructor de la clase hija.
Finalmente, podemos observar que “ProductoGranel” define una función
“obtenerPrecio”, que es exactamente la misma que ya existía en la clase “Producto”.
Sin embargo, su código cambia sustancialmente: ahora el precio se calcula
como el producto de los atributos “precio” (por kilo) y “peso”:
double obtenerPrecio() {
return precio * peso;
}
La nueva función sustituirá a la heredada de “Producto”. Este mecanismo nos
permite redefinir una función para que se ajuste a las características de la nueva
clase. En el caso de la clase “ProductoGranel”, este cambio era necesario,
pues la función “obtenerPrecio” que había heredado simplemente devolvía el
valor del atributo “precio”.
El mecanismo de extensión nos permite utilizar objetos de la clase “ProductoGranel”
como si fueran de la clase “Producto”. El código siguiente imprime el
nombre y el precio de varios productos independientemente de su tipo:
class Producto {
int código;
CC BY • PID_00174495 39 Introducción a la programación orientada a objetos
String descripción;
double precio;
// el constructor: inicializa el objeto Producto
Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
// devuelve la descripción del producto
String obtenerDescripción() {
return descripción;
}
}
class ProductoGranel extends Producto {
// añade el atributo peso (del producto)
double peso;
// el constructor también añade el parámetro peso
ProductoGranel(int código, String descripción,
double precio, double peso) {
super(código, descripción, precio);
this.peso = peso;
}
// para los productos vendidos a granel, el precio es
// igual al resultado de multiplicar el peso (en Kg) por
// el precio por Kg
double obtenerPrecio() {
return precio * peso;
}
}
int código;
CC BY • PID_00174495 39 Introducción a la programación orientada a objetos
String descripción;
double precio;
// el constructor: inicializa el objeto Producto
Producto(int código, String descripción, double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
double obtenerPrecio() {
return precio;
}
// devuelve la descripción del producto
String obtenerDescripción() {
return descripción;
}
}
class ProductoGranel extends Producto {
// añade el atributo peso (del producto)
double peso;
// el constructor también añade el parámetro peso
ProductoGranel(int código, String descripción,
double precio, double peso) {
super(código, descripción, precio);
this.peso = peso;
}
// para los productos vendidos a granel, el precio es
// igual al resultado de multiplicar el peso (en Kg) por
// el precio por Kg
double obtenerPrecio() {
return precio * peso;
}
}
public class Caja {
// muestra por pantalla el precio de un producto
public static void escribirPrecio(Producto p) {
System.out.println(p.obtenerDescripción() + " " +
p.obtenerPrecio() + " EUR");
}
public static void main(String[] args) {
Producto sal;
ProductoGranel mango, salmón;
// crea los productos sal, salmón y mango
sal = new Producto(80005355, "Sal", 0.60);
salmón = new ProductoGranel(80005373, "Salmón",
9.55, 0.720);
mango = new ProductoGranel(80005312, "Mango", 2.99,
0.820);
// escribe el precio de los tres productos
escribirPrecio(sal);
escribirPrecio((Producto)salmón);
escribirPrecio((Producto)mango);
}
}
// muestra por pantalla el precio de un producto
public static void escribirPrecio(Producto p) {
System.out.println(p.obtenerDescripción() + " " +
p.obtenerPrecio() + " EUR");
}
public static void main(String[] args) {
Producto sal;
ProductoGranel mango, salmón;
// crea los productos sal, salmón y mango
sal = new Producto(80005355, "Sal", 0.60);
salmón = new ProductoGranel(80005373, "Salmón",
9.55, 0.720);
mango = new ProductoGranel(80005312, "Mango", 2.99,
0.820);
// escribe el precio de los tres productos
escribirPrecio(sal);
escribirPrecio((Producto)salmón);
escribirPrecio((Producto)mango);
}
}
Como podemos observar, en la llamada a la función “escribirPrecio” para los
objetos de la clase “ProductoGranel” hay una peculiaridad: el uso del operador
de conversión:
escribirPrecio((Producto)mango)
El operador de conversión permite cambiar el tipo de una variable a otro tipo
compatible (casting, en inglés). En el ejemplo, forzamos a que “salmón” y
“mango” se traten como si fuesen de la clase “Producto”. El operador de conversión
toma la forma del tipo destino encerrado entre paréntesis justamente
delante de la variable que deseamos convertir.
El resultado será el siguiente:
Sal 0.60 EUR
Salmón 6.876 EUR
Mango 2.4518 EUR
Salmón 6.876 EUR
Mango 2.4518 EUR
2.6. Los paquetes y la directiva import
Para estructurar el código y evitar conflictos con los nombres de las clases, el
lenguaje Java nos permite agrupar las clases en paquetes (packages).
Normalmente, los paquetes agrupan clases que están relacionadas por alguna
funcionalidad o característica. Por ejemplo, se suelen crear paquetes para agrupar
las clases que implementan la interfaz de usuario de una aplicación o
aquellas que proporcionan algún tipo de cálculo matemático o probabilístico.
La agrupación de clases en paquetes nos permite:
• Poner de manifiesto la relación entre un conjunto de clases.
Identificar un conjunto de clases con una funcionalidad.
Evitar los conflictos con los nombres de las clases: puede haber clases homónimas
en paquetes distintos.
Para crear un paquete, basta con escribir en la parte superior de un fichero
fuente la palabra package seguida del nombre del paquete. Por ejemplo, las lí-
neas siguientes:
package postgradosig;
class Asignatura {
// (...)
class Asignatura {
// (...)
definen una clase llamada “Asignatura” perteneciente al paquete “postgra- a
dosig”.
Los paquetes se pueden dividir en subpaquetes de manera arbitraria, formando
una jerarquía de paquetes. Las líneas siguientes:
package edu.uoc.postgradosig;
class Asignatura {
// (...)
}
class Asignatura {
// (...)
}
Cuando desde una clase se desea acceder a otra clase situada en otro paquete,
es necesario indicarlo de forma explícita mediante la directiva import (importar).
De hecho, esta directiva no se hace otra cosa que importar la definición
de la clase.
Supongamos la clase “Alumno” perteneciente al paquete “edu.uoc.postgradosig.alumno”
y la clase “Asignatura” perteneciente al paquete “edu.uoc.postgradosig.asignatura”.
Supongamos también que la clase “Asignatura” tiene
una función “matricular” que permite matricular a un alumno a la asignatura.
Entonces, el código de la clase “Asignatura” sería parecido al siguiente:
package edu.uoc.postgradosig.asignatura;
// importa la clase “Alumno”
import edu.uoc.postgradosig.alumno.Alumno;
class Asignatura {
// (...)
public void matricular(Alumno alumno) {
// (...)
}
}
// importa la clase “Alumno”
import edu.uoc.postgradosig.alumno.Alumno;
class Asignatura {
// (...)
public void matricular(Alumno alumno) {
// (...)
}
}
2.7. Visibilidad y encapsulación
Hasta ahora hemos accedido a los atributos y las funciones de un objeto libremente,
sin ninguna restricción. Sin embargo, como los atributos contienen el
estado de un objeto, no suele ser deseable que código ajeno a la clase pueda
acceder a ellos y modificarlos, porque, al desconocer el papel que desempeñan
cada uno de los atributos en la implementación de la clase, esto podría dejar
el objeto en un estado erróneo.
Se llama encapsulación a la técnica consistente en ocultar el estado de un objeto,
es decir, sus atributos, de manera que sólo pueda cambiarse mediante un
conjunto de funciones definidas a tal efecto.
Los atributos y las funciones de una clase pueden incorporar en su definición
un modificador que especifique la visibilidad de dicho elemento. Hay tres modificadores
explícitos en Java:
public: el elemento es público y puede accederse a él desde cualquier clase,
protected: el elemento es semiprivado y sólo pueden acceder a él la clase
que lo define y las clases que lo extienden, y
private: el elemento es privado y sólo puede acceder a él la clase que lo define.
Si no se especifica ningún nivel de visibilidad, se aplica la regla de visibilidad
implícita (conocida como package): el atributo o función será público para todas
las clases del mismo paquete, y privado para las demás.
A modo de ejemplo, veamos una nueva versión de la clase “Producto”:
class Producto {
private int código;
private String descripción;
private double precio;
// el constructor: inicializa el objeto Producto
public Producto(int código, String descripción,
double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
public void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
public double obtenerPrecio() {
return precio;
}
// devuelve la descripción del producto
public String obtenerDescripción() {
return descripción;
}
}
private int código;
private String descripción;
private double precio;
// el constructor: inicializa el objeto Producto
public Producto(int código, String descripción,
double precio) {
this.código = código;
this.descripción = descripción;
this.precio = precio;
}
// fija el precio del producto
public void fijarPrecio(double precioNuevo) {
precio = precioNuevo;
}
// devuelve el precio del producto
public double obtenerPrecio() {
return precio;
}
// devuelve la descripción del producto
public String obtenerDescripción() {
return descripción;
}
}
Como se puede apreciar, se han declarado todos los atributos privados y las
funciones públicas. Esto implica que la sentencia
sal.precio = 0.60;
en la que “sal” es una instancia de la clase “Producto”, será inválida. En su lugar,
debemos usar una llamada a la función “cambiarPrecio” creada a tal efecto:
sal.cambiarPrecio(0.60);
Es importante remarcar que al menos un constructor de la clase debe ser pú-
blico. En caso contrario, no se va a poder instanciar ningún objeto de la clase.
Bibliografía
Oracle, "The Java(tm) Tutorials", http://download.oracle.com/javase/tutorial/.
Oracle, "Java(tm) 2 Platform Standard Edition 5.0 API Specification", http:// download.oracle.com/javase/1.5.0/docs/api/.
Personalización
SIG, Albert Gavarró Rodríguez , Universidad Oberta de Catalunya, España 2011
No hay comentarios:
Publicar un comentario