Un lenguaje de programación de propósito general, tipado, orientado a objetos,… que permite el desarrollo desde aplicaciones básicas, pasando por aplicaciones empresariales hasta aplicaciones móviles.
Se creó para que pudiese ser multiplataforma y multidispositivo, bajo el paradigma “Write Once Run Anywhere” (WORA). Gracias a esta característica un programa Java escrito una vez podemos ejecutarlo sobre diferentes plataformas, siendo soportados los sistemas operativos Windows, MacOs y UNIX. Y a su vez en diferentes tipos de dispositivos.
Para poder seguir este paradigma la compilación de un programa Java no genera lenguaje máquina, si no que genera bytecodes. Estos bytecodes son interpretados por una máquina virtual o JVM (Java Virtual Machine) y es esta la que traduce los bytecodes al código fuente que se ejecuta en el dispositivo final.
ESTRUCTURADO
Se trata de una sencilla forma de programar que se basa en la combinación de 3 tipos de estructuras que son capaces de expresarlo todo.
INDEPENDIENTE DE LA PLATAFORMA
Cuando compilamos código fuente Java no se genera código máquina específico, si no que se genera bytecodes, los cuales son interpretados por la Java Virtual Machine (JVM), posibilitando que un mismo código fuente pueda ser ejecutado en múltiples plataformas.
ORIENTADO A OBJETOS
Cualquier elemento del lenguaje Java es un objeto. Dentro de los objetos se encapsulan los datos, los cuales son accedidos mediante métodos.
Una variable es un contenedor de información.
Las variables pueden almacenar valores enteros, números decimales, caracteres, cadenas de caracteres (palabras o frases), etc.
El contenido de las variables puede cambiar durante la ejecución del programa, de ahí viene el nombre de “variable”.
Java es un lenguaje fuertemente tipado, es decir, es necesario declarar todas las variables que utilizará el programa, indicando siempre el nombre y el tipo de cada una.
Los nombres de las variables deben ser significativos, es decir, deben indicar perfectamente qué información contienen.
Las variables que van a contener números enteros se declaran con int. Si pretendemos almacenar valores muy grandes en una variable, usaremos el tipo long en lugar de int.
Usamos los tipos double o float cuando queremos (o esperamos) almacenar números con decimales en las variables. La diferencia está en la precisión, las variables de tipo double tienen mayor precisión que las de tipo float.
Se pueden almacenar números enteros en variables de tipo double o float.
Las cadenas de caracteres se utilizan para almacenar palabras y frases. Todas las cadenas de caracteres deben ir entrecomilladas mediante el símbolo de comillas dobles (“).
Una cadena de caracteres puede contener cero (cadena vacía) o más caracteres. Nos podríamos encontrar con un código como el siguiente: String cadenaInicial = “”;
Las cadenas de caracteres se utilizan para almacenar palabras y frases. Todas las cadenas de caracteres deben ir entrecomilladas mediante el símbolo de comillas dobles (“).
Hay que tener en cuenta que no es lo mismo "a" que 'a'. Aunque el contenido en ambos casos es la letra “a”, lo primero es una cadena de caracteres y lo segundo es un carácter. En algunos lenguajes de programación se pueden usar indistintamente las comillas simples y las dobles pero en Java tienen un significado muy distinto.
Los tipos int, long, double y float vistos anteriormente son los llamados “primitivos”.
Estos tipos forman parte del propio lenguaje y están disponibles desde las primeras versiones. En Java hay 8 tipos primitivos.
En Java se puede operar con las variables de una forma muy parecida a como se hace en matemáticas. Los operadores aritméticos de Java son los siguientes:
La sentencia de asignación se utiliza para dar un valor a una variable. En Java se utiliza el símbolo igual ( = ) para este cometido. Por ejemplo x = 7 + 1 es una asignación en la cual se evalúa la parte derecha 7 + 1, y el resultado de esa evaluación se almacena en la variable que se coloque a la izquierda del igual, es decir, 8 se almacena en x.
La sentencia x + 1 = 23 * 2 no es una asignación válida ya que en el lado izquierdo debemos tener únicamente un nombre de variable.
Para recoger datos por teclado usamos System.console().readLine(). Cuando llegamos a esta sentencia, el programa se detiene y espera que el usuario introduzca información mediante el teclado. La introducción de datos termina con la pulsación de la tecla INTRO. Una vez que el usuario presiona INTRO, todo lo que se ha tecleado se almacena en una variable, en el siguiente ejemplo esa variable es nombre.
ATENCIÓN - Este código solo funciona ejecutando nuestro programa desde la consola con la instrucción java DimeTuNombre.java
Fíjate que en el programa anterior la sentencia s.nextLine() sería el equivalente a System.console().readLine()
La clase Scanner funciona tanto en entornos integrados como en una ventana de terminal.
Mediante el uso de la clase Scanner es posible leer varios datos en una misma línea. En el programa anterior se pedía un nombre y una edad, en total dos datos que había que introducir en líneas separadas. Observa cómo en el siguiente ejemplo se piden esos dos datos en una sola línea y separados por un espacio.
La sentencia if permite la ejecución de una serie de instrucciones en función del resultado de una expresión lógica.
El resultado de evaluar una expresión lógica es siempre verdadero (true) o falso (false). Es muy simple, en lenguaje natural sería algo como ”si esta condición es verdadera entonces haz esto, sino haz esto otro”.
Una sentencia condicional permite al programa bifurcar el flujo de ejecución de instrucciones dependiendo del valor de una expresión.
Estos operadores siempre devolverán un tipo de dato boolean. Un boolean puede tomar dos valores, verdadero o falso. Al realizar la comparación entre dos valores, se obtendrá como resultado true o false.
Los operadores de comparación se pueden combinar con los operadores lógicos. Por ejemplo, si queremos saber si la variable a es mayor que b y además es menor que c, escribiríamos if((a > b) && (a < c)).
El comportamiento de los operadores lógicos se muestra en la siguiente tabla donde V significa verdadero y F significa falso.
En el siguiente programa puedes ver el uso de operadores lógicos combinado con operadores relacionales (operadores de comparación). Intenta adivinar cuál será el resultado mirando el código.
A veces es necesario comparar el valor de una variable con una serie de valores concretos. La selección múltiple es muy parecida (aunque no es exactamente igual) a una secuencia de varias sentencias if.
El formato de switch es el que se muestra a continuación. En lenguaje natural sería algo así como “Si variable vale valor1 entonces entra por case valor1:, si variable vale valor2 entonces entra por case valor2:,... si variable no vale ninguno de los valores que hay en los distintos case entonces entra por default:.
Una sentencia condicional permite al programa bifurcar el flujo de ejecución de instrucciones dependiendo del valor de una expresión.
Los bucles se utilizan para repetir un conjunto de sentencias. Por ejemplo, imagina que es necesario introducir las notas de 40 alumnos con el fin de calcular la media, la nota máxima y la nota mínima. Podríamos escribir 40 veces la instrucción que pide un dato por teclado s.nextLine() y convertir cada uno de esos datos a un número con decimales con 40 instrucciones Double.parseDouble(), no parece algo muy eficiente. Es mucho más práctico crear un bucle aquellas sentencias que queremos que se repitan.
Normalmente existe una condición de salida, que hace que el flujo del programa abandone el bucle y continúa justo en la siguiente sentencia. Si no existe condición de salida o si esta condición no se cumple nunca, se produciría lo que se llama un bucle infinito y el programa no terminaría nunca.
Se suele utilizar cuando se conoce previamente el número exacto de iteraciones(repeticiones) que se van a realizar. La sintaxis es la siguiente:
Justo al principio se ejecuta expresion1 y normalmente se usa para inicializar una variable. El bucle se repite mientras se cumple expresion2 y en cada iteración del bucle se ejecuta expresion3, que suele ser el incremento o decremento de una variable. Con un ejemplo se verá mucho más claro.
EJEMPLO
int i = 1 se ejecuta solo una vez, antes que cualquier otra cosa; como ves, esta expresión se utiliza para inicializar la variable i a 1. Mientras se cumpla la condición i < 11 el contenido del bucle, o sea, System.out.println(i); se va a ejecutar.
En cada iteración del bucle, i++ hace que la variable i se incremente en 1. El resultado del ejemplo es la impresión en pantalla de los números del 1 al 10.
Vamos a DEPURAR el código.
Ahora prueba con con i = i + 2.
El bucle while se utiliza para repetir un conjunto de sentencias siempre que se cumpla
una determinada condición. Es importante reseñar que la condición se comprueba al
comienzo del bucle, por lo que se podría dar el caso de que dicho bucle no se ejecutase
nunca. La sintaxis es la siguiente:
Las sentencias se ejecutan una y otra vez mientras la expresión sea verdadera.
EJEMPLO 1
El siguiente ejemplo produce la misma salida que el ejemplo anterior, muestra cómo cambian los valores de i del 1 al 10.
EJEMPLO 2
En el siguiente ejemplo se cuentan y se suman los números que se van introduciendo por teclado. Para indicarle al programa que debe dejar de pedir números, el usuario debe introducir un número negativo; esa será la condición de salida del bucle. Observa que el bucle se repite mientras el número introducido sea mayor o igual que cero.
Un array es un tipo de dato capaz de almacenar múltiples valores. Se utiliza para agrupar datos muy parecidos, por ejemplo, si se necesita almacenar la temperatura media diaria en Málaga durante el último año se pueden utilizar las variables temp0, temp1, temp2, temp3, temp4, ... y así hasta 365 variables distintas pero sería poco práctico; es mejor utilizar un array de nombre temp y usar un índice para referenciar la temperatura de un día concreto del año.
En matemáticas, un array de una dimensión se llama vector.
Veamos con un ejemplo cómo se crea y se utiliza un array.
Observa que el índice de cada elemento de un array se indica entre corchetes de tal forma que n[3] es el cuarto elemento ya que el primer índice es el 0.
Fíjate que la definición del array y la reserva de memoria para los cuatro elementos que la componen se ha realizado en dos líneas diferentes.
Normalmente se abrevian estas dos líneas para quedarse en una sola. A efectos prácticos es exactamente lo mismo..
Ejemplo de uso de arrays.
El array x del ejemplo anterior se ha ido rellenando elemento a elemento. Si se conocen
previamente todos los valores iniciales del array, se puede crear e inicializar en una sola línea.
Cada elemento del array se puede utilizar exactamente igual que cualquier otra variable, es decir, se le puede asignar un valor o se puede usar dentro de una expresión. En el siguiente ejemplo se muestran varias operaciones en las que los operandos son elementos del array num.
Para recorrer todos los elementos de un array se suele utilizar un bucle for junto con un índice que va desde 0 hasta el tamaño del array menos 1.
Un array bidimensional utiliza dos índices para localizar cada elemento. Podemos ver este tipo de dato como un array que, a su vez, contiene otros arrays. También se puede ver como una cuadrícula en la que los datos quedan distribuidos en filas y columnas.
Mediante la línea int[][] n = new int[3][2] se define un array bidimensional de 3 filas por 2 columnas, pero bien podrían ser 2 filas por 3 columnas, según el objetivo y el uso que del array haga el programador.
Los valores del array bidimensional se pueden proporcionar en la misma línea de la definición como se muestra en el siguiente ejemplo.
Los arrays bidimensionales se utilizan con frecuencia para situar objetos en un plano como por ejemplo las piezas de ajedrez en un tablero, o un personaje de video-juego en un laberinto.
Al trabajar con arrays es muy frecuente cometer errores utilizando los índices. El error más típico consiste en intentar acceder a un elemento mediante un índice que se sale de los límites. Por ejemplo, si tenemos el array n definido de la siguiente forma int[] n = new int[10], cuando intentamos acceder a n[-1] o a n[10] obtenemos un error.
Para recorrer un array de un modo más práctico y sencillo, sin que tengamos que preocuparnos de los límites, podemos utilizar el bucle for con el formato foreach. De esta forma indicamos simplemente el nombre del array que queremos recorrer y en qué variable se va a ir colocando cada elemento con cada iteración del bucle. No hay que especificar con qué índice comienza y termina el bucle, de eso se encarga Java.
Fíjate en el segundo for; en este caso no se utiliza ningún índice; simplemente decimos:
“ve sacando uno a uno los elementos del array nota y deposita cada uno de esos elementos en la variable n que es de tipo double”.
En programación es muy frecuente reutilizar código, es decir, usar código ya existente. Cuando una parte de un programa requiere una funcionalidad que ya está implementada en otro programa no tiene mucho sentido emplear tiempo y energía en implementarla otra vez.
Una función es un trozo de código que realiza una tarea muy concreta y que se puede incluir en cualquier programa cuando hace falta resolver esa tarea. Opcionalmente, las funciones aceptan una entrada (parámetros de entrada) y devuelven una salida.
Ejemplo
Observa el siguiente ejemplo. Se trata de un programa que pide un número por teclado y luego dice si el número introducido es o no es primo.
Podemos intuir que la tarea de averiguar si un número es o no primo será algo que utilizaremos con frecuencia más adelante así que podemos aislar el trozo de código que realiza ese cometido para usarlo con comodidad en otros programas.
Las funciones de un determinado tipo (por ejemplo funciones matemáticas) se pueden agrupar para crear un paquete (package) que luego se importará desde el programa que necesite esas funciones.
Cada paquete se corresponde con un directorio. Por tanto, si hay un paquete con nombre matematicas debe haber un directorio llamado también matematicas en la misma ubicación del programa que importa ese paquete (normalmente el programa principal).
Las funciones se pueden agrupar dentro de un paquete de dos maneras diferentes. Puede haber subpaquetes dentro de un paquete; por ejemplo, si quisiéramos dividir las funciones matemáticas en funciones relativas al cálculo de áreas y volúmenes de figuras geométricas y funciones relacionadas con cálculos estadísticos, podríamos crear dos directorios dentro de matematicas con nombres geometria y estadistica respectivamente. Estos subpaquetes se llamarían matematicas.geometria y matematicas.estadistica. Otra manera de agrupar las funciones dentro de un mismo paquete consiste en crear varios ficheros dentro de un mismo directorio. En este caso se podrían crear los ficheros Geometria.java y Estadistica.java.
Ejemplo
Crea un paquete matemáticas y a continuación las siguientes clases y pruebalas desde tu código principal
Se pueden hacer por valor o por referencia:
En el paso por valor, el valor de un parámetro de la función se copia en otra variable. Por el contrario, en el paso por referencia, se pasan/copian los parámetros reales a la función.
En Java no se puede elegir el tipo de paso de parámetros. Si el parámetro pasado es de un tipo de datos primitivo se pasa por valor, en el caso de ser no primitivo (objetos, arrays) el paso se hará por referencia.
El primer ejemplo pasa los parámetros por valor y el segundo por referencia.
El ámbito de una variable es el espacio donde “existe” esa variable o, dicho de otro modo, el contexto dentro del cual la variable es válida.
Seguramente has utilizado muchas veces variables con nombres como x, i, max o aux. El ámbito de cada una de esas variables era el programa en el que estaban declaradas. Aunque se llamen igual son variables diferentes.
Cuando se implementan funciones hay que tener muy claro que las variables utilizadas como parámetros (por valor) o las variables que se definen dentro de la función son locales a esa función, es decir, su ámbito es la función y fuera de ella esas variables no existen.
La programación orientada a objetos es un paradigma de programación que se basa, como su nombre indica, en la utilización de objetos. Estos objetos también se suelen llamar instancias.
Un objeto en términos de POO no se diferencia mucho de lo que conocemos como un objeto en la vida real. Pensemos por ejemplo en un coche. Nuestro coche sería un objeto concreto de la vida real, igual que el coche del vecino, o el coche de un compañero de trabajo, o un deportivo que vimos por la calle el fin de semana pasado... Todos esos coches son objetos concretos que podemos ver y tocar.
Tanto mi coche como el coche del vecino tienen algo en común, ambos son coches. En este caso mi coche y el coche del vecino serían instancias (objetos) y coche (a secas) sería una clase. La palabra coche define algo genérico, es una abstracción, no es un coche concreto sino que hace referencia a unos elementos que tienen una serie de propiedades como matrícula, marca, modelo, color, etc.; este conjunto de propiedades se denominan atributos o variables de instancia.
Clase
Concepto abstracto que denota una serie de cualidades, por ejemplo coche.
Instancia
Objeto palpable, que se deriva de la concreción de una clase, por ejemplo mi coche.
Atributos
Conjunto de características que comparten los objetos de una clase, por ejemplo para la clase coche tendríamos matrícula, marca, modelo, color y número de plazas.
En Java, los nombres de las clases se escriben con la primera letra en mayúscula mientras que los nombres de las instancias comienzan con una letra en minúscula. Por ejemplo, la clase coche se escribe en Java como Coche y el objeto “mi coche” se podría escribir como miCoche.
Definiremos cada clase en un fichero con el mismo nombre más la extensión .java, por tanto, la definición de la clase Coche debe estar contenida en un fichero con nombre Coche.java
En JAVA se utiliza la nomenclatura CAMELCASE. Puedes revisar como se usa en el siguiente enlace
Uno de los pilares en los que se basa la Programación Orientada a Objetos es el encapsulamiento. Básicamente, el encapsulamiento consiste en definir todas las propiedades y el comportamiento de una clase dentro de esa clase; es decir, en la clase Coche estará definido todo lo concerniente a la clase Coche y en la clase Libro estará definido todo lo que tenga que ver con la clase Libro.
El encapsulamiento parece algo obvio, pero hay que tenerlo siempre muy presente al programar utilizando clases y objetos. En alguna ocasión puede que estemos tentados a mezclar parte de una clase con otra clase distinta para resolver un problema puntual. No hay que caer en esa trampa. Se deben escribir los programas de forma que cada cosa esté en su sitio. Sobre todo al principio, cuando definimos nuestras primeras clases, debemos estar pendientes de que todo está definido donde corresponde.
La ocultación es una técnica que incorporan algunos lenguajes (entre ellos Java) que permite esconder los elementos que definen una clase, de tal forma que desde otra clase distinta no se pueden “ver las tripas” de la primera. La ocultación facilita, como veremos más adelante, el encapsulamiento.
Las acciones asociadas a una clase se llaman métodos. Estos métodos se definen dentro del cuerpo de la clase y se suelen colocar a continuación de los atributos.
Los métodos determinan el comportamiento de los objetos.
Los atributos de una clase se declaran igual que las variables que hemos venido usando hasta ahora, pero hay una gran diferencia entre estos atributos y las variables que aparecen en el main (programa principal). Una variable definida en el cuerpo del programa principal es única, sin embargo cada uno de los objetos que se crean en el programa principal tienen sus propios atributos; es decir, si en el programa principal se crean 20 objetos una clase, cada uno tiene sus valores para los atributos de esa clase.
Getter
Se trata de métodos muy simples, su cometido es devolver el valor de un atributo. Podrían tener cualquier nombre pero en Java es costumbre llamarlos con la palabra get (obtener) seguida del nombre del atributo.
Setter
Este tipo de métodos tiene el cometido de establecer un valor para un determinado atributo. De nuevo podrían tener cualquier nombre pero en Java es costumbre llamarlos con la palabra set (asignar) más el nombre del atributo.
En Java existe una solución muy elegante para mostrar información sobre un objeto por pantalla. Si se quiere mostrar el contenido de la variable entera x se utiliza System.out.print(x) y si se quiere mostrar el valor de la variable de tipo cadena de caracteres nombre se escribe System.out.print(nombre). De la misma manera, si se quiere mostrar el objeto miVehiculo que pertenece a la clase Vehiculo, también se podría usar System.out.print(miVehiculo). Java sabe perfectamente cómo mostrar números y cadenas de caracteres pero no sabe a priori cómo se pintan los vehículos. Para indicar a Java cómo debe pintar un objeto de la clase Vehiculo basta con implementar el método toString dentro de la clase.
El método equals(), se utiliza para comparar dos objetos. Ojo no confundir con el operador ==, que ya sabemos que sirve para comparar también, equals compara si dos objetos apuntan al mismo objeto.
Equals() se usa para saber si dos objetos son del mismo tipo y tienen los mismos datos. Nos dará el valor true si son iguales y false si no. Las subclases pueden sobrescribir el método equals() para hacer una comparación entre dos objetos.
En la lista de argumentos del método equals() hay que pasarle un argumento de tipo Object. sino se sobrecarga el método, no se sobreescribe.
Al definir los elementos de una clase, se pueden especificar sus ámbitos (scope) de visibilidad o accesibilidad con las palabras reservadas public (público), protected (protegido) y private (privado). En la siguiente tabla se muestra desde dónde es visible/accesible un elemento (atributo o método) según el modificador que lleve.
La herencia es una de las características más importantes de la POO. Si definimos una serie de atributos y métodos para una clase, al crear una subclase, todos estos atributos y métodos siguen siendo válidos.
Por ejemplo, de una clase vehículo, podríamos crear subclases como coche o moto. Por defecto, estas clases podrán utilizar los métodos y atributos definidos en la clase padre. A su vez, podríamos crear nuevas clases que heredasen de la clase coche como por ejemplo turismo, monovolumen, furgoneta, etc ... y todas estas clases heredarían los métodos de la clase padre.
Clase abstracta: es aquella que no va a tener instancias de forma directa, aunque si habrá instancias de sus subclases.
Un método se puede redefinir (volver a definir con el mismo nombre) en una subclase. En estos casos, indicaremos nuestra intención de sobrescribir un método mediante la etiqueta @Override.
Si no escribimos esta etiqueta, la sobre escritura del método se realizará de todas formas ya que @Override indica simplemente una intención.
POLIMORFISMO
En Programación Orientada a Objetos, se llama polimorfismo a la capacidad que tienen los objetos de distinto tipo (de distintas clases) de responder al mismo método.
Crea un nuevo proyecto JAVA y crea la siguiente estructura de clases en un paquete con el nombre vehiculos.