Prog. Aprendiendo POO

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

  • La idea de esta página es la de mostrar muchos de los conceptos de la POO siguiendo el desarrollo de un ejercicio.




Clases

  • Un programador novato (PN) decide introducirse en el mundo de POO y tratar de implementar una solución informática para la gestión de los vehículos de la empresa de transporte donde trabaja.
Decide usar como lenguaje de programación Java.
  • El problema que al que está intentando dar solución es el siguiente: Necesita almacenar información sobre los diferentes vehículos disponibles en la Compañía.
Entre esta información está el año de compra del vehículo, su matrícula y su marca.
  • El PN decide implementar en una clase la información necesaria.
Recuerda que las clases no son más que abstracciones de las propiedades de los objetos sin dar ningún valor concreto.
Por convención, las clases tienen que estar definidas en singular y la la primera letra en mayúscula así como las primeras letras de las siguientes palabras si el nombre está formado por varias.
Más tarde utiliza la clase creada previamente en otra clase llamada 'Principal', una que se va a ejecutar (dispone del método main)
Dentro de esta clase, el PN creará los cuatro vehículos que tiene la empresa en este momento y le asignará los siguientes valores:
Prog ej novato 1.jpg
  • Una vez hecho esto, el PN compila sus clases (Vehiculo y Principal) y ejecuta la clase Principal, creando 4 instancias de la clase Vehiculo.


Ejercicio 1: Implementar la solución.

Solución Ejercicio 1



Métodos sin parámetros

  • Comprueba que no le haya dado errores, pero permanece con la duda de que si habrá guardado los valores de forma correcta, por lo que es necesario que se muestren estos valores.
Para imprimir los valores, necesita un procedimiento (método) que los saque a la pantalla.
Entonces decide crear dentro de la clase Vehiculo, un método que imprima la información Puede hacer un único método que imprima el año, matrícula y marca a la vez, pero decide crear tres métodos, uno para cada tipo de información a imprimir, de nombre imprimirAno, imprimirMatricula, imprimirMarca, porque necesita mostrar la información en forma individualizada.
  • Una vez escritos, decide probarlos, para ver si muestran la información correcta,
Para esto modifica la clase Principal, haciendo que cada objeto de la clase Vehículo llame a cada uno métodos.


Ejercicio 2: Implementar la solución.

Solución Ejercicio 2



Herencia

  • El PN está bastante satisfecho con lo que se ha logrado hasta ahora, pero esto fue solo una pequeña parte de lo que tienes que hacer.
Digamos que su empresa se dedica al transporte de 'dinero', y dos de sus vehículos son furgonetas. Cada una de ellas es capaz de transportar una cantidad de dinero.
Además, cada furgonete lleva consigo un número de vigilantes jurados
  • Esta información es necesaria almacenarla.
Ante esta situación, el PN decide crear una nueva clase, Furgoneta, con los procedimientos de la clase Vehículo y los nuevos atributos (cantidad de dinero y número de vigilantes jurados).
Pero cuando iba a comenzar a implementar esta solución, aparece el Programador Experto, alguien que ha trabajado en Java durante muchos años, y viendo lo que iba a hacer, le explica que en Java, al ser un lenguaje de POO se puede implementar algo que se denomina Herencia.
  • La herencia se da entre dos clases y es un tipo de relación jerárquica de tipo es un y se denomina generalización o especialización.
En la herencia, hacemos que una clase (subclase) pueda hacer uso de todos los métodos y atributos definidos en la clase de la que va a heredar (superclase).
El otro tipo de relación 'jerárquica' es el de tipo es parte de denominada de agregación o inclusión. Es un tipo de relación que heredó de la asociación.


  • Con esta gran ventaja, el PN decide crear una subclase Furgoneta de la superclase Vehiculo.
Dentro de la nueva clase implementa los nuevos atributos (cantidad de dinero y número de vigilantes jurados), ya que son atributos de la clase Furgoneta, que no tiene la clase Vehículo.
  • Por otro lado, como el PN querrá mostrar la información de estos dos nuevos atributos, también crea, dentro de la clase Furgoneta, dos métodos para mostrar estos atributos del nombre 'imprimirDinero()', 'imprimirJurados()'.
  • Para probar esta nueva clase, tendrá que cambiar su clase Principal, ya que tenía cuatro objetos de la clase Vehículo, pero ahora sabe que dos de ellos son furgonetas, por lo que tendrá que cambiar su código, y si antes tenía cuatro vehículos de forma:
Vehiculo vehiculo1 = new Vehiculo();
Vehiculo vehiculo2 = new Vehiculo();
Vehiculo vehiculo3 = new Vehiculo();
Vehiculo vehiculo4 = new Vehiculo();
Ahora tendrá dos furgonetas y dos vehículos de la forma:
Vehiculo vehiculo1 = new Vehiculo();
Vehiculo vehiculo2 = new Vehiculo();
Furgoneta furgoneta1 = new Furgoneta();
Furgoneta furgoneta2 = new Furgoneta();
  • A cada uno de sus vehículos (furgonetas y vehículos) les asignará los valores a sus atributos y llamará a los métodos que imprimen estos valores.
El PN realiza todo lo anterior, compila las clases y ejecuta la clase principal, comprobando que todo sea correcto.


Ejercicio 3: Implementar la solución.

Solución Ejercicio 3




  • El PN ahora piensa que sus otros dos vehículos (que son coches) no llevan dinero, pero llevan documentos secretos. Cada coche lleva un documento secreto, por lo que es necesario para guardar su nombre, así como el nombre del conductor que conduce el coche
Pensando de la misma manera que el caso de las furgonetas, ahora sabe que puede crea una subclase (llamada Coche, por ejemplo) de la superclase Vehiculo.
En esa subclase estarán los atributos nombreCond y nombreDoc. Por lo tanto, implementa esta subclase y los dos atributos, así como los métodos necesarios para mostrarlos (imprimirNombreDoc, imprimirNombreCond)


  • Una vez hecho esto, el PN modifica la clase Principal, tal como lo hizo en el caso de Furgonetas para probar todo de nuevo.


Ejercicio 4: Implementar la solución.

Solución Ejercicio 4



Constructores

  • Repasemos lo que hemos hecho hasta este momento.
  • Creamos 3 clases (Vehiculos, Coche y Furgoneta), para almacenar la información que necesito, así como los métodos necesarios para imprimir (mostrar) esa información.
  • Por otro lado, creamos otra clase Principal, que hará uso de las clases anteriores, mediante la creación de 4 vehículos, dos de tipo Coche y dos de tipo Furgoneta.
  • Cada uno de estos vehículos es una instancia de la clase correspondiente y tendrá almacenados algunos valores específicos. Si quisiera tener más información almacenada de otros vehículos, solo tendría que crear un nuevo objeto (instanciar) de la clase correspondiente.


  • El PN todavía está rompiéndose el 'coco' para implementar completamente todo el proceso de su empresa
Lo siguiente que piensa es en 'refinar' un poco más lo hecho hasta ahora.
Primero, sería conveniente tener un método que dé valores a las variables o atributos que tenemos en cada clase.
Es decir, en lugar de poner:
coch1.marca = "Peugot";
coch1.matricula = "C-1234-BZ";
coch1.ano = 1999;
coch1.nombreDoc = "Documento secreto 1";
coch1.nombreCond = "Ángel";
se podría llamar un método con 5 parámetros, tal que cada uno de estos parámetros asignará un valor a cada atributo, de la siguiente manera:
coch1.asignarValores (param1, param2, param3, param4, param5)
donde:
  • marca = param1
  • matricula = param2
  • ano = param3
  • nombreDoc = param4
  • nombreCond = param5
para que pudiéramos poner en una sola línea lo que teníamos antes en cinco:
coch1.asignarValores ("Peugot", "C-1234-BZ", 1999, "Documento secreto 1","ANGEL");
  • A la luz de esta brillante idea, el PN está listo para implementarla.
Primero, piensa dónde colocar el método que asigna los valores de los parámetros a los atributos y de inmediato se da cuenta que no puedes ubicarlo en la clase Vehiculo, ya que en esta clase no existen los atributos nombreDoc y nombreCond, por lo que decide colocarlos en las subclases.
Entonces, en la subclase Coche, decide crear un método de cinco parámetros, en el que se asignan los valores a las cinco variables o atributos que tenemos, de la forma:
coch1.asignarValores (param1, param2, param3, param4, param5)
donde:
  • marca = param1
  • matricula = param2
  • ano = param3
  • nombreDoc = param4
  • nombreCond = param5
y crear otro método en la subclase Furgoneta con otros cinco parámetros de la forma:
coch1.asignarValores (param1, param2, param3, param4, param5)
donde:
  • marca = param1
  • matricula = param2
  • ano = param3
  • dinero = param4
  • numJurados = param5


  • Una vez hecho esto, podemos substituir las líneas:
coch1.marca = "Peugot";
coch1.matricula = "C-1234-BZ";
coch1.ano = 1999;
coch1.nombreDoc = "Documento secreto 1";
coch1.nombreCond = "Ángel";
por
coch1.asignarValores ("Peugot", "C-1234-BZ", 1999, "Documento secreto 1","ANGEL");


y las líneas:
fur1.marca = "Opel";
fur1.matricula = "C-2342-AF";
fur1.ano = 1996;
fur1.dinero = 1000;
fur1.numJurados = 1;

por

fur1.asignarValores(Opel,C-2342-AF,1996,1000,1)
  • NOTA: Si queréis podéis probarlo de esta manera, pero la implementación final se va a realizar de otra forma.



  • Pero el Programador Experto vuelve a recoger una pluma que olvidó, y observa lo que el PN está a punto de hacer.
- !!!!! Pero ¿ qué haces ? ! Cómo se ve que llevas programando Java desde hace poco tiempo !
  • En la POO para realizar la asignación inicial de variables, se puede usar un método especial, llamado constructor, que se llama cada vez que se crea una nueva instancia, es decir, cada vez que se crea un objeto de una clase.
El nombre del constructor se llama igual que la clase, no se indica valor de retorno (void,boolean...) solo su nombre, y puede o no tener parámetros. También puede llevar modificadores de acceso (public, protected, vacío o privated, este último no tiene sentido ponerlo). Los modificadores de acceso los daremos posteriormente, por ahora indicar que siempre vamos a poner 'public'.


Frente al asombro y la incomprensión del PN (aparte de ponerse rojo como uno tomate), el Programador Experto le comenta:
- Te daré un ejemplo para que entiendas: Supongamos que tengo una clase llamada Prueba, con dos atributos (atrib1,atrib2) y que dichos atributos tengan un valor de 10 por defecto.
Podría hacerlo en la definición del atributos, pero también puedo hacer uso del constructor, al que se llama de forma automática cuando se crea un objeto:
 1 public class Prueba{
 2 
 3   int atrib1,atrib2;
 4 
 5   public Prueba(){
 6      atrib1 = 10;
 7      atrib2 = 10;
 8   }
 9 
10 }
Ahora en la clase Principal:
public class Principal{

   public static void main(String arg[]){

      Prueba miPrueba = new Prueba();      // En este momento llama al constructor
   }

}


  • Este constructor siempre está ahí, y si el usuario no lo define, java asigna el valor 0 (numéricos), false (booleanos) y null (de referencia) para todos los atributos de la clase para ese objeto.
  • En este momento, miPrueba.atrib1 tiene el valor 10, al igual que miPrueba.atrib2.
El PN antes de esta demostración de sabiduría no puede decir nada más que:
- Ahhhhhhhhhhhhhhhhhhh
- Pero esto no es todo, dice el programador experto. Resulta que puedes tener definidos tantos constructores como quieras, siempre y cuando estos se diferencien en el número o tipo de argumentos (dado que el nombre es fijo, debe ser el mismo nombre que la clase).
Entonces, podríamos tener otro constructor para asignar valores específicos a los dos atributos de la forma:
 1 public class Prueba{
 2 
 3   int atrib1,atrib2;
 4 
 5   public Prueba(){
 6      atrib1 = 10;
 7      atrib2 = 10;
 8   }
 9 
10   public Prueba(int valor1, int valor2){    // El nombre del constructor tiene que ser el mismo que el de la clase
11      atrib1 = valor1;
12      atrib2 = valor2;
13   }
14 
15 }
De tal forma que ahora puedo asignar un valor concreto a cada atributo de la forma:
1 public class Principal{
2 
3    public static void main(String arg[]){
4 
5       Prueba miPrueba = new Prueba(10,40);      // Llama al segundo constructor. Ahora atrib1 vale 10 y atrib2 vale 40
6    }
7 
8 }


  • A esto se llama sobrecargar un método (overload).
Es decir, tener dentro de uno clase varios métodos con el mismo nombre, pero con diferente tipo o número de argumentos le dice el programador experto con un cierto aire de superioridad ...
El PN se pone risueño como un pájaro al entender lo que el Programador Experto le explica (aunque un poco mosqueado).
Ahora sabe que en el caso de la clase Coche y la clase Furgoneta, debe crear un constructor que tenga cinco parámetros, asignando a cada atributo el valor del parámetro.
A cada parámetro le asigna un nombre: param1, param2, .... y más tarde dentro del constructor, asigna los valores de estos parámetros a cada variable.
Entonces en la clase Coche tendrá 5 parámetros que se asignarán a la marca, ano, matricula, nombreCond y nombreDoc y en el caso de Furgoneta, se asignarán a la marca, año, matricula, dinero y númJurados.


Ejercicio 5: Implementar la solución.

Solución Ejercicio 5




  • NOTA: En caso de que usemos la herencia, y creamos un constructor en la subclase, esto hará que llame automáticamente al constructor predeterminado (el que no lleva parámetros) de la clase superior (superclase), hasta llegar al constructor de clase que estamos llamando.
Gráficamente se vería así:
Prog ej novato 2.jpg


En este caso, si creo un objeto de la ClaseC de la forma:
ClaseC miObjeto = new ClaseC();
Dará como resultado:
Hola desde A
Hola desde B
Hola desde C


  • El problema vendrá cuando en una clase superior (ClaseB) definamos un constructor diferente al predeterminado (no lleva parámetros).
En ese caso, al intentar crear un objeto de una clase que se deriva de él (Clase C), será necesario definir el constructor predeterminado de la clase superior (Clase B) aunque no haga nada.
Por ejemplo, supongamos que ClaseB tiene un constructor de la forma: ClaseB(String param1).
En este caso, si creamos un objeto de la ClaseC, será necesario haber definido el constructor por defecto en la ClaseB. Si no queremos hacer ninguna operación, lo definiremos de la forma:
ClaseB () {}
Y lo mismo tendría que hacerse para la Clase A en el caso de definir un constructor diferente de al constructor por defecto (sin parámetros).



Referencia super y this

  • Bueno, dice el PN, esto va.
La verdad es que de esta manera he ahorrado mucho código en las asignaciones, pero aún hay algo que no me cuadra.
Si miro el código, resulta que asignamos los atributos de de la clase Vehiculo en la clase Coche y Furgoneta, y que este código se repite.
Si aplico el mismo concepto de constructor, podría tener un constructor en la clase Vehiculo, que asigna los valores iniciales a los tres atributos que pertenecen a esta clase, dejando a los constructores de las clases Coche y

Furgoneta con la asignación de sus atributos correspondientes.

Esto estaría genial, ¿verdad? ¿ pero puedo hacerlo? ¿ cómo puedo llamar al constructor de la clase Vehículo desde el constructor de la clase Coche y la clase Furgoneta ?


  • El PN inicia una búsqueda exhaustiva con la documentación que posee y después de muchas tazas de café con unas pocas gotitas de whisky, descubre que en Java se pueden utilizar dos palabras claves:
  • this: es una referencia a la instancia actual (al objeto actual). Es decir, supongamos que tenemos un objeto de la clase Coche (miCoche). Este objeto es una instancia de la clase Coche.
Si yo asigno a la variable miCoche.marca = "Citroen" y posteriormente llamo a un método de la forma miCoche.metodo() en el que tengo la instrucción this.marca, me estoy refiriendo a los valores de la instancia (objeto) que llamó a este método (en este caso miCoche). 'this' es el equivalente a miCoche, es decir, se podría llamar a cualquier método o referenciar a cualquier variable del objeto miCoche.
Se suele utilizar para distinguir entre parámetros y atributos en caso de que se llamen igual.
Por ejemplo:
1 public void metodo(int edad)
2 {
3    this.edad = edad; /* this.edad hace referencia al atributo edad de la clase */
4 }
  • super: hace referencia a los métodos y variables de la superclase. Sólo se utiliza en caso de tener dos clases relacionadas mediante una herencia, en la que dentro de la subclase queramos referenciar algún atributo y método de la superclase.
  • El PN se pone muy contento y decide tomarse una cervecita por haber descubierto esta información tan útil.
Esto le va a arreglar dos problemas.
  • Primero, modificará los nombre de los paramétros en los dos constructores creados, ya que con los que tenía, a simple vista no se enteraba para que servían (el nombre no era muy significativo).
Les dará los mismos nombres que los atributos de la clase, con lo que deberá utilizar la palabra this para diferenciar unos de otros.
Ejercicio 6: Implementar la solución.
  • Segundo, creará un constructor en la clase Vehículo, con tantos parámetros como atributos tiene la clase Vehiculo, para llamarlo desde clase Coche y Furgoneta. Este constructor asignará los valores de los parámetros a los atributos de su clase.
¿ Cómo lo va a hacer ? Haciendo uso de la palabra super . Desde el constructor de cada uno de las clases anteriores llamará al constructor de la clase superior (Vehiculo), de la forma: super (param1, param2, param3);
Para dar más significado a los nombres de los parámetros, estos tienen el mismo nombre que los atributos, como lo hemos hecho en el paso anterior, por lo tanto, la llamada tendrá el siguiente formato: super (anho, marca, matricula)
Nota: Será necesario crear el constructor predeterminado en las clases superiores.


Ejercicio 7: Implementar la solución.


Solución Ejercicio 6 y 7

Atributos y métodos estáticos

  • El PN está tan contento consigo mismo que decide ir a enseñarle al programador experto lo que ha logrado.
Éste le dice:
- Bueno, veo que estás cogiendo el espíritu del POO, pero aún te queda mucho camino por recorrer.
Voy a plantearle un problema a tu modelo, a ver sí puedes encontrar la solución
El PN comienza a sudar ...
- Supongamos que en tu modelo deseas guardar información sobre la cantidad de vehículos de la compañía.
Digamos, que esta información es común para todos los vehículos. ¿Cómo lo resuelves?
  • El PN regresa a su despacho con 'la cola entre las piernas', pero decide investigar para ver si puedes dar con la solución.
La primera idea que tiene es utilizar un atributo numVehiculos en la clase Vehiculo, pero inmediatamente se da cuenta de que esto tiene un problema
Si la información es común a todos los vehículos, este atributo es propio de cada objeto que se crea a partir de la clase, es decir, si tengo cuatro vehículos, tendría que asignar cuatro veces el mismo número de automóviles, y si quiero modificarlo, tendría que modificarlo en los cuatro vehículos (instancias de la clase => objetos).
  • Sumergiéndose en la documentación de Java encuentra la solución.
- ¡EUREKA !!!!!
Resulta que todas las variables que he creado hasta ahora, se les denomina variables de instancia, ya que cada objeto (instancia) almacena la información de cada objeto (instancia de clase), pero existen otro tipo de variables (también se aplica a los métodos) denominadas variables de clase cuyo valor es común a todos los objetos de esa clase.
Es decir, son como variables globales, pero a nivel de objetos en una clase.
Para declarar una variable de clase, debemos añadir un modificador a la variable de la forma: static tipo nombreVariable;


  • Ahora solo tengo que poner el atributo numVehiculos en la clase Vehiculo (no tengo que ponerlo en otra clase, ya que la variable es común a las dos subclases) y que sea de tipo estático (static).
Cada vez que creo un vehículo, tendré que aumentar su valor en uno.
Este valor será común a todos los objetos de la clase.
También crearé un método que imprima ese valor (impNumVehiculos). Dicho método, también será estático y de esta forma no necesitaré un objeto para poder llamarlo (lo veremos a continuación)
Por otro lado, para acceder al valor de una variable de clase, puedo hacerlo a través de objeto de la forma habitual (miObjeto.variable = value) o través de la clase (Vehiculo.variable = value).
Puedo crear los cuatro vehículos y modificar el valor de la variable para cada uno de los objetos creados en la forma:
miCoche.numVehiculos ++; o miFurgoneta.numVehiculos ++;


  • ¿ Por qué? Porque es una variable de clase y por lo tanto es común para cualquier objeto (instancia) de la clase Vehiculo. También podría hacer referencia a la misma directamente con el nombre de la clase Vehiculo o con la clase Coche o con la clase Furgoneta (al ser estas dos, subclases de Vehiculo) de la siguiente forma:
Coche.numVehiculos++ o Furgoneta.numVehiculos++ o Vehiculo.numVehiculos++
El PN se dispone a implantar la solución.


Ejercicio 8: Implementar la solución.

Solución Ejercicio 8





  • Lo que ha hecho el programador novato hasta ahora es algo parecido a esto:
 1 Coche coch1 = new Coche(1999,"C-1234-BZ","Peugot","Angel","Documento Secreto 1");
 2 coch1.impNumVehiculos();
 3 
 4 Coche.numVehiculos++;
 5 Coche coch2 = new Coche(1998,"C-4325-AZ","Fiat", "Juan","Documento Secreto 2");
 6 Coche.numVehiculos++;
 7 coch2.impNumVehiculos();
 8 
 9 Furgoneta fur1 = new Furgoneta(1996,"C-2342-AF","Opel", 1000,1);
10 Furgoneta.numVehiculos++;
11 fur1.impNumVehiculos();
12 
13 Furgoneta fur2 = new Furgoneta(2000,"C-4322-CF","Ford",5000,3);
14 Furgoneta.numVehiculos++;
15 fur2.impNumVehiculos();



  • Pero esto se podría mejorar, ya que si cada vez que se instancia un Vehiculo, ejecutamos el constructor de Vehiculo, podríamos incrementar en este método el valor de la variable numVehiculos, eliminando así todas las líneas en las que incrementamos de forma 'manual' el valor de la variable.
Pues manos a la obra....


Ejercicio 9: Implementar la solución.

Solución Ejercicio 9




  • Bien, bien –dice el PN- pero resulta que el mismo concepto puede ser aplicado a los métodos, creando métodos de clase, que pueden ser llamados sin necesidad de tener un objeto de la misma como tengo hasta ahora. Esto puede ser muy interesante, ya que puedo imprimir el número de vehículos que tengo sin necesidad de utilizar objetos de la clase. La forma de llamar a un método de clase es:
NombreClase.método();
Y la forma de definir un método de clase es:
static public void impNumCoches(){....}
  • Lógicamente un método de clase también se puede llamar de la forma tradicional, a través del objeto.
El PN va a cambiar el método impNumCoches a método de clase, y lo llamará para hacer una prueba de cualquiera de las formas posibles (son 4, desde la clase Vehiculo, desde la clase Coche, desde la clase Furgoneta y desde los objetos miCoche y miFurgoneta).


Ejercicio 10: Implementar la solución.

Solución Ejercicio 10


Ejercicio 11: Averigua qué métodos de la clase String son estáticos.

Comprueba como desde Netbeans puedes ver su definición.


Ejercicio 12: Busca otra clase en Java que tenga algún método de clase.

Comprueba como desde Netbeans puedes ver su definición.



Encapsulación

  • El PN se dirige con la solución a ver al Programador Experto.
-Lo tengo. Ya he dado con la solución. Me costó un poco, pero ya lo tengo.
Le enseña la solución.
- Muy bien, dice el Programador Experto. Se ve que verdaderamente estás cogiendo el truco a esto de la POO. Creo que dentro de poco ya estarás preparado para empezar a realizar aplicaciones Java.
Sólo te falta ver qué son las clases abstractas así como la seguridad, lo que Java llama encapsulación, es decir, como hacer que los atributos o métodos tengan su acceso limitado.
Creo que es mejor que mires la parte de encapsulación y el tema de las clases abstractas lo trateremos más adelante.
  • El PN se pone muy contento de que está ya tan cerca del fin y se considera casi un igual con respecto al Programador Experto (nada más lejos de la realidad).
Mirando la documentación de Java, descubre que para limitar el acceso a las variables o métodos de una clase existe 3 modificadores: public, private o protected.
Prog ej novato 3.jpg
  • Podemos observar en el cuadro anterior que cuando definimos un método o atributo vamos a poder anteponerle un modificador de acceso, que puede ser public,private, protected o no poner nada.
Hasta ahora hemos definido los atributos de esta forma, sin poner ningún modificador de acceso.
  • Para entender el cuadro debemos partir de que vamos a tener una clase, en la que vamos a tener definidos atributos y métodos a los que vamos a poder asignar uno de los modificadores de acceso anteriores.
  • Lo que indican las columnas es:
  • Class: Acceso desde el propio código de la clase. Es decir, si tengo un atributo con el modificador 'private', ese atributo va a poder ser utilizado dentro de la propia clase.
  • Package: Acceso desde cualquier clase o subclase que se encuentre en el mismo paquete.
  • Subclass: Subclase que se encuentra en un paquete diferente.
  • World: Resto de clases, es decir, clases que no son subclases, que se encuentran en paquetes diferentes.



Ejemplo con private

  • Podemos observar en el cuadro como un atributo o método definido como private sólo es accesible desde dentro de la clase.
Definimos la clase ClaseA en el paquete 'misclases' con un atributo con el modificador 'private'. Podemos observar como dentro del código de la clase ClaseA podemos acceder al atributo y mostrar o modificar su valor:
 1 package misclases;
 2 
 3 public class ClaseA{
 4   private String nombre;
 5 
 6   public void imprimirNombre(){
 7     System.out.printf("El nombre es:%s", nombre);
 8   }
 9   public void setNombre(String nombre){
10     this.nombre = nombre;
11   }
12 
13 }


Si ahora creo una subclase de la clase ClaseA dentro del mismo paquete, dentro del código de la clase no puedo 'acceder' al atributo nombre:
1 package misclases;
2 
3 public class ClaseB extends ClaseA{
4 
5   public void imprimirNombre(){
6     System.out.printf("El nombre en la clase B es: %s", nombre);  // ERROR => NO PUEDO ACCEDER
7   }
8 
9 }


Si ahora creo una clase que vaya a hacer uso de la ClaseA, por ejemplo una Principal con el método main, tampoco voy a poder acceder al atributo:
 1 package misclases;
 2 
 3 public class Principal{
 4 
 5 
 6   public static void main(String arg[]) {
 7 
 8     ClaseA clase1 = new ClaseA();
 9 
10     clase1.nombre = "Angel";  // ERROR => NO PUEDO ACCEDER
11   }
12 
13 }


Lo mismo pasaría con cualquier clase que haga uso de la ClaseA pero en otro paquete, tanto si es una subclase como si no.


  • Este es el modificador que normalmente aplicaremos a los atributos, ya que la idea es que el acceso a los mismos siempre se realice a través de los métodos get y set.



Ejemplo con protected

  • Podemos observar en el cuadro como un atributo o método definido como protected es accesible desde dentro del código de la clase, desde subclases y clases dentro del mismo paquete y desde subclases definidas en otros paquetes.
Definimos la clase ClaseA en el paquete 'misclases' con un atributo con el modificador 'protected'. Podemos observar como dentro del código de la clase ClaseA podemos acceder al atributo y mostrar o modificar su valor:
 1 package misclases;
 2 
 3 public class ClaseA{
 4   protected String nombre;
 5 
 6   public void imprimirNombre(){
 7     System.out.printf("El nombre es:%s", nombre);
 8   }
 9   public void setNombre(String nombre){
10     this.nombre = nombre;
11   }
12 
13 }


Si ahora creo una subclase de la clase ClaseA dentro del mismo paquete, dentro del código de la clase puedo 'acceder' al atributo nombre:
1 package misclases;
2 
3 public class ClaseB extends ClaseA{
4 
5   public void imprimirNombre(){
6     System.out.printf("El nombre en la clase B es: %s", nombre); 
7   }
8 
9 }


Si ahora creo una clase que vaya a hacer uso de la ClaseA, por ejemplo una Principal con el método main, puedo acceder al atributo:
 1 package misclases;
 2 
 3 public class Principal{
 4 
 5 
 6   public static void main(String arg[]) {
 7 
 8     ClaseA clase1 = new ClaseA();
 9 
10     clase1.nombre = "Angel"; 
11   }
12 
13 }


Si cambio el paquete de la ClaseB, puedo acceder al atributo ya que un atributo protected en una subclase en otro paquete tiene acceso:
 1 package otrasclases;
 2 
 3 import misclases.ClaseA;
 4 
 5 public class ClaseB extends ClaseA{
 6 
 7   public void imprimirNombre(){
 8     System.out.printf("El nombre en la clase B es: %s", nombre);  // Da un error, no tenemos acceso a la variable.
 9   }
10 
11 }



Si ahora creo una clase que vaya a hacer uso de la ClaseA, por ejemplo otra Principal en otro paquete diferente, no puedo acceder al atributo:
 1 package otrasclases;
 2 
 3 import misclases.ClaseA;
 4 
 5 public class Principal{
 6 
 7 
 8   public static void main(String arg[]) {
 9 
10     ClaseA clase1 = new ClaseA();
11 
12     clase1.nombre = "Angel";   // ERROR => NO PUEDO ACCEDER
13   }
14 
15 }




Ejemplo con modificador 'vacío'

  • Si observamos el cuadro, podemos ver que la fila que tenemos que mirar es la tercera ('no modifier').
En esa fila podemos ver como dentro del código de la clase vamos a poder acceder al atributo (primera columna), cualquier clase o subclase definida dentro del mismo paquete (segunda columna) va a poder acceder al atributo. Las subclases que estén definidas en otro paquete y el resto de clases (clases definidas en otros paquetes) no podrán acceder al atributo.


Definimos la clase ClaseA en el paquete 'misclases' con un atributo sin modificador:
 1 package misclases;
 2 
 3 public class ClaseA{
 4   String nombre;
 5 
 6   public void imprimirNombre(){
 7     System.out.printf("El nombre es:%s", nombre);
 8   }
 9 
10 }


Si ahora creo una subclase de la clase ClaseA dentro del mismo paquete, dentro del código de la clase puedo 'acceder' al atributo nombre:
1 package misclases;
2 
3 public class ClaseB extends ClaseA{
4 
5   public void imprimirNombre(){
6     System.out.printf("El nombre en la clase B es: %s", nombre);
7   }
8 
9 }


Si ahora creo una clase que vaya a hacer uso de la ClaseA, por ejemplo una Principal con el método main, también voy a poder acceder al atributo ya que la clase está dentro del mismo paquete que la ClaseA:
 1 package misclases;
 2 
 3 public class Principal{
 4 
 5 
 6   public static void main(String arg[]) {
 7 
 8     ClaseA clase1 = new ClaseA();
 9 
10     clase1.nombre = "Angel";
11   }
12 
13 }


Si cambio el paquete de la ClaseB, no podré acceder al atributo ya que un atributo en una subclase en otro paquete no tiene acceso:
 1 package otrasclases;
 2 
 3 import misclases.ClaseA;
 4 
 5 public class ClaseB extends ClaseA{
 6 
 7   public void imprimirNombre(){
 8     System.out.printf("El nombre en la clase B es: %s", nombre);  // Da un error, no tenemos acceso a la variable.
 9   }
10 
11 }


Lo mismo pasaría con cualquier clase que hago uso de la ClaseA pero en otro paquete.



Ejemplo con modificador public

  • En este caso no merece la pena poner ningún ejemplo, ya que vamos a poder tener acceso al atributo/método desde cualquier parte.



Encapsulación (continuación)

  • Comentar que las propias clases pueden tener el modificador public o el vacío.
Recordar que en un archivo físico pueden estar definidas muchas clases, pero sólo una de ellas debe de tener el modificador 'public' y debe corresponderse con el nombre físico de la clase.


Ejercicio 14: Implementar la solución.

  • Tal como comentamos al explicar el modificador 'private' haz que todos los atributos de todas las clases de vehiculos sean privados o sin modificador (deberás elegir) y crea un método get/set para cada uno de ellos (recuerda hacerlo utilizando el NetBeans, que los puede generar automáticamente).
Modifica los métodos si crees que es necesario.
Después modifica los constructures para que asignen los valores a los atributos utilizando los métodos 'setXXXX'.


Solución Ejercicio 14




  • Siguiendo con las aventuras del programador novato....
  • Todo esto le parece un poco lioso, así que le pregunta a su hermano, que trabajó en otro lenguaje de POO qué significa y para que sirve:
- Hermanito, tengo un pequeño problema en el trabajo. No entiendo para nada esto de los tipos de acceso a variables y métodos en la POO de Java. ¿ Me lo puedes explicar ?
Claro. Realmente son formas de limitar el acceso a la información que incorpora Java, para realizar un programación más robusta. Desde luego no necesitarías implantar nada de esto y tu programa funcionará igual, pero gracias a esta ayuda puedes limitar el número de veces que tu programa fallará si tus clases son usadas por otras personas que puedan hacer un mal uso de ellas.
- Que interesante (dice el PN). Pues esto me viene al dedillo con lo siguiente que tengo que implantar. Resulta que es necesario calcular el dinero que ingresamos por los servicios de nuestros automóviles.
Tengo que almacenar dos informaciones.
  • Primero el dinero que ingresamos por cada automóvil
  • Segundo el dinero total que ingresamos que será la suma de los ingresos de los automóviles.


  • La forma de calcular el dinero de cada automóvil es en el caso de las furgonetas, en función de la cantidad de dinero que se lleva.
  • Así, si es < 100.000 ingresamos 10.000
  • Si es >= 100.000 ingresamos 25.000 (¡!MENUDO ABUSO!!).
En el caso de los coches, el dinero que se le pide va en función de la importancia del documento que lleve.
Este dato no estaba contemplado hasta ahora, por lo que tendré que incorporarlo como un atributo de los coches y modificar el constructor para asignarle un valor el iniciar (instanciar) cada coche.
La importancia está representado por un número:
  • Si importancia = 0 ingresamos 5000
  • Si importancia = 1 ingresamos 10000
  • Si importancia = 2 ingresamos 20000
(usar sentencia switch)
  • Para implantar todo esto, que es lo que tengo que hacer
(NOTA: pensar si sois capaces de implementarlo)



  • Primero tendré que incorporar la variable importancia, de tipo byte, a la clase Coches, ya que es un atributo propio de los coches y no tiene nada que ver con las furgonetas.
Esta variable es de instancia (no de clase) ya que cada coche tiene su propio valor en función del documento que transporte.
Una vez hecho esto, tendré que modificar el constructor de la clase Coche para asignarle un valor a la importancia del documento, igual que hicimos con los otros atributos.
Recordar que para no tener que hacer una conversión desde la clase Pricipal, declararemos el tipo como int y haremos un cast posterior.


Después tendré que crear una variable ingreso de tipo double, la cual almacenará el dinero que recaudará cada Automóvil.
Esta variable estará definida en la clase Vehiculo, ya que es común tanto a Coches como Furgonetas (los dos tienen unos ingresos en función de lo que transporte) y es una variable de instancia, ya que cada Vehiculo tendrá unos valores concretos.
  • Más tarde tendré que crear un procedimiento que asigne los ingresos en función de lo que lleva cada Vehículo. Este procedimiento es propio a cada clase (Coches y Furgonetas) ya que se calcula de diferente forma.
Por tanto crearé un método calcularIngreso() tanto en la clase Coche como en la clase Furgoneta. Este método modificará el valor de la variable ingresos en función de la importancia en el caso de los coches y en función del dinero en el caso de las furgonetas.


  • -Que divertido es esto (exclama el PN)
Ahora necesito implantar un método que imprima lo ingresado por cada vehículo.
Dicho método estará definido en la clase Vehículo, ya que es común a las dos clases.




Ejercicio 15: Implementar la solución.

Solución Ejercicio 15



  • Ahora queda el problema de los ingresos totales. Este dato estará almacenado en otra variable (ingresosTotal) que será de tipo double y que además será una variable de clase pues es una información común a todos los vehículos, no es propio de cada uno de ellos.
  • A continuación, crearé un método calcularIngresosTotal que estará definido en la clase Vehiculo (es común a Coche y Furgoneta) el cual sumará el valor de la variable ingreso (de cada vehiculo) a la variable ingresosTotal.
  • Ahora tengo que modificar los métodos calcularIngresos de las clases Coche y Furgoneta, para que llamen el método calcularIngresos de la clase Vehiculos.
  • Una vez hecho esto crearé un método para imprimir el valor de la variable ingresosTotal que se llame impIngresosTotal(), definiéndolo como método de clase.



Ejercicio 16: Implementar la solución.

'Solución Ejercicio 16




  • Bien, dice el PN, pero ahora es cuando entra en juego esto de la privacidad. Resulta que no quiero que se pueden modificar los valores que establecen los ingresos de los vehículos (es decir, el dinero en el caso de las furgonetas y la importancia en el caso de los coches). Estos valores sólo deben ser asignados en el momento de la creación de los vehiculos (instanciar la clase => new) y no se debería de poder cambiarse.
Para hacer esto sólo debo reasignar dichas variables como private.


  • Por otra parte, esto me impide modificar los valores de dichas variables, pero nada me impide llamar tantas veces como quiera al método calcularIngresos de los coches y las furgonetas, incrementando el valor de ingresosTotales.
Para evitar esto, también declaro los métodos como private, y los llamaré desde el constructor de la clase Coche y la clase Furgoneta (cuando creamos los vehiculos).
Ahora ya no tengo que llamar al método de la forma: fur1.calcularIngreso();
Sino que a la hora de hacer el new, automáticamente ya se me calcula la ganancia de ese coche y de la empresa.
  • Ahora sólo tengo que hacer lo mismo con la variable ingresosTotales.
La declaramos como private, para que no podamos modificarla si no es a través de un método de la propia clase (calcularIngresosTotales).
Sin embargo, no puedo poner como private el método calcularIngresosTotales, ya que si lo hiciese, no sería heredado por lo que no existiría para las clases Coches y Furgonetas.
Sí podríamos ponerlo como protected, de tal forma que si una clase de otro paquete hace uso de nuestra clase, no podría llamar a dicho método.



Ejercicio 17: Implementar la solución.

'Solución Ejercicio 17




  • Bien, esto está de miedo. Ya tengo más o menos claro los conceptos de la POO.


  • Aún quedan varios conceptos que iremos viendo a medida que programemos.
Sólo apuntar que las constantes en Java se declaran con el modificador final antes del tipo de datos de la forma: final tipo nombre_constante = valor_inicial;
  • También podemos declarar Clases como final, indicando que no se pueden derivar (no puede existir una subclase) y también métodos final, es decir, métodos que no se puedan sobrescribir (recordar que en una subclase podemos definir el mismo método que en la superclase, indicando @Override)


  • Siguiendo nuestro ejercicio, hacer que las cantidades que se asignan a los ingresos sean constantes.


Ejercicio 18: Implementar la solución.

'Solución Ejercicio 18



Paquetes

  • Haz que todas las clases creadas hasta ahora estén guardadas en un paquete de nombre transportes.
Modifica la clase Principal para hacer uso de las clases del paquete.


Ejercicio 19: Implementar la solución.

'Solución Ejercicio 19

Ejercicio Final

  • Vamos a realizar un repaso a todo lo dado durante este curso.
Para ello se propone realizar un ejercicio muy similar al anterior, pero basado en otra temática.


  • Una empresa dedicada a la venta de material deportivo, quiera implantar una aplicación informática, la cual es encargada al pelota del Jefe, para haber si lo da hecho.
Dicho pelota no tiene mucha idea de programación, así que no sabe en que lenguaje implantarlo, pero sus ‘colaboradores’ le aconsejan que en Java, por ser un lenguaje de programación muy fácil e intuitivo.
El ‘pelota’ que llamaremos 'Van Fal', realiza un análisis de su empresa para determinar que información necesita guardar.
  • Entre otros productos destacamos los siguientes:
  • Bicicletas
  • Patines
  • Raquetas de tenis
  • Pelotas, tanto de fútbol como de baloncesto.
  • En todos los artículos es necesario llevar cuenta de un stock (numero de artículos).
En caso de que el stock sea menor que 5 unidades, es necesario que el programa avise del hecho cada vez que se produce una venta.
Ejercicio final 1.jpg
  • Nuestro programa debe ser capaz de mostrar el número de artículos que hay actualmente. Dicho número se podrá aumentar, pero siempre en un número múltiplo de 5. En caso contrario, se debe avisar del error.
  • Por otra parte, también debe existir un método que nos permita vender artículos. El número de artículos que podemos vender de una sola vez no debe superar a 2.
En caso contrario se debe avisar del error.
Cada vez que realicemos una venta, debemos llevar cuenta de las ganancias, tanto totales, como las debidas a cada producto individual.
  • Cada producto cuesta lo siguiente:
Ejercicio final 2.png


  • Por otra parte, es necesario almacenar información relativa a cada artículo.
Así, en la bicicleta es necesario almacenar:
  • La marca
  • Número de marchas
  • Si las ruedas son de montaña o no.
En los patines:
  • Si son en línea o no
  • Su marca.
  • Si las ruedas son de plástico o de hierro
  • Su peso.
En las raquetas de tenis:
  • Su marca
  • Si son de aluminio o de madera
  • Su peso
  • Su longitud.
En las pelotas de fútbol:
  • Su marca
  • Su peso
  • Su tipo de material, el cual sólo puede ser plástico o cuero.
  • Para todos los atributos debe haber un método que muestre su valor.
Los atributos deben tener el modificador de acceso adecuado y debe de crearse los métodos set/get de todos ellos.


  • Los productos que tenemos en la empresa son los siguientes:
Ejercicio final 3.png


  • Mostrar las características de todos los artículos. Si todo está bien pasar a lo siguiente.


  • Guarda todas las clases relacionadas con el material deportivo en un paquete de nombre material.deportivo.


  • Una vez implantado todo, simular lo que pasaría en la siguiente secuencia temporal:
  • Un cliente se lleva una bicicleta y una pelota de fútbol.
  • Otro cliente se intenta llevar 3 pelotas de fútbol. ¿ qué pasa ?
  • Se reponen 5 pelotas de fútbol y 3 bicicletas ¿ qué pasa ?
  • Tu jefe te dice que le muestres las ganancias que se obtuvieron por las bicicletas, y lo que se lleva ganado por todos los productos vendidos.




-- Ángel D. Fernández González -- (2017).