static: lo que comparten todos los objetos
En resumen: Un campo o un método static pertenece a la clase, no al objeto: todos los objetos ven un único valor compartido, por ejemplo un contador de los objetos creados.
Problema
Todos los productos son el n.º 1
La tienda quiere que cada producto nuevo reciba el número siguiente: 1, 2, 3… Pones un contador nextId en la clase. El constructor le da al producto el número actual y aumenta el contador:
class Product { private int nextId = 1; private int id; private String name;
public Product(String name) { this.name = name; id = nextId; nextId++; }
@Override public String toString() { return "n.º " + id + " " + name; }}
Product bread = new Product("Pan");Product milk = new Product("Leche");Product coffee = new Product("Café");System.out.println(bread);System.out.println(milk);System.out.println(coffee);n.º 1 Pann.º 1 Lechen.º 1 CaféTodos son el n.º 1. La razón es que nextId es un campo del objeto, igual que el nombre. Cada producto nuevo recibe su propio contador, que empieza en 1. El contador del pan nunca llega a la leche.
La idea
Un campo de la clase, no del objeto
El contador no es parte de un producto. Lo comparten todos los productos. Añade static:
private static int nextId = 1;Un campo static es un campo de la clase. Hay uno solo, sin importar cuántos objetos crees, y todos los objetos lo comparten:
class Product { private static int nextId = 1; private int id; private String name;
public Product(String name) { this.name = name; id = nextId; nextId++; }
@Override public String toString() { return "n.º " + id + " " + name; }}
public class Main { public static void main(String[] args) { Product bread = new Product("Pan"); Product milk = new Product("Leche"); Product coffee = new Product("Café"); System.out.println(bread); System.out.println(milk); System.out.println(coffee); }}n.º 1 Pann.º 2 Lechen.º 3 CaféEl pan toma el 1 y deja el contador en 2. La leche ve el mismo contador, toma el 2 y lo deja en 3.
private static int nextId = 1;
public Product(String name) { this.name = name; id = nextId; nextId++;}
Product bread = new Product("Pan");Product milk = new Product("Leche");Product coffee = new Product("Café");Paso a paso, en palabras
- nextId es static: un campo en la clase, no uno en cada objeto.
- new Product("Pan"): un objeto nuevo. El constructor guarda el nombre.
- id = nextId: el objeto toma el número actual, 1.
- nextId++ cambia el contador compartido: ahora es 2.
- Leche: el mismo contador. Toma el 2 y lo deja en 3.
- Café: toma el 3 y lo deja en 4.
- Hay un solo contador para todos, así que los números van en orden.
Si el campo es public static, llegas a él desde fuera a través del nombre de la clase (Product.nextId), no a través de un objeto.
Pruébalo
Compartido y propio
¿Qué imprimirá el programa?
class Order { public static int count = 0; public int items = 0;
public Order() { count++; items++; }}
Order a = new Order();Order b = new Order();Order c = new Order();System.out.println(Order.count + " " + c.items);count es static: un contador para todos los pedidos, aumentado tres veces. items es un campo del objeto: cada pedido tiene el suyo, aumentado una vez.La idea
Un método static: trabajar sin objeto
Lo mismo vale para los métodos. Un método static es un método de la clase. Lo llamas a través del nombre de la clase, sin new:
class Calculator { public static int add(int a, int b) { return a + b; }
public static double average(int[] values) { int sum = 0; for (int v : values) { sum += v; } return (double) sum / values.length; }}
System.out.println(Calculator.add(10, 20));int[] grades = { 5, 6, 4, 6 };System.out.println(Calculator.average(grades));System.out.println(Math.max(3, 8));305.258Calculator.add no necesita un objeto: todo lo que necesita llega como parámetros. Los métodos de Math también son así: Math.max, Math.sqrt. Nadie escribe new Math().
Ahora está claro por qué main es static. Cuando el programa arranca, todavía no hay objetos. El entorno de ejecución llama a main directamente a través de la clase. Por eso también eran static los métodos del módulo «Métodos», que llamábamos desde main.
Más precisamente, para curiosos
Lo contrario también funciona: en un método static puedes crear un objeto y llamar a sus métodos. Es justo lo que hacemos en main desde el principio del módulo.
Pruébalo
Encuentra el error
Elige la línea con el error.
label() lee name, y name pertenece a un producto concreto. Un método static no tiene objeto, así que no sabe de quién tomar el nombre:error: non-static variable name cannot be referenced from a static contextstatic: la etiqueta pertenece al producto, así que es un método de instancia.La idea
Constantes
El IVA es del 20 % para todos los productos, y no cambia mientras el programa se ejecuta. Un valor así es una constante:
class Prices { public static final double VAT_RATE = 0.20;
public static double withVat(double price) { return price * (1 + VAT_RATE); }}
System.out.println(Prices.withVat(50));System.out.println("IVA: " + Prices.VAT_RATE * 100 + "%");60.0IVA: 20.0%static final significa «de la clase» y «no se puede cambiar». En Java, los nombres de las constantes van en mayúsculas con guiones bajos: VAT_RATE.
Si alguien intenta cambiarla,
Prices.VAT_RATE = 0.25;el programa no compila:
error: cannot assign a value to static final variable VAT_RATEGanas dos veces: el número 0.20 está escrito en un solo lugar con un nombre claro, y nadie puede cambiarlo por error.
Más precisamente, para curiosos
A veces el valor es distinto para cada objeto, pero no debe cambiar después de crear el objeto. El número del producto es así:
private static int nextId = 1;private final int id;
public Product(String name) { this.name = name; id = nextId; nextId++;}Un campo final (sin static) se fija una vez, en el constructor, y después no cambia. Intentar id = 5; en un método no compila.
Pruébalo
¿Qué falta?
class Calendar {
public static int MONTHS = 12;
}final: con static final, el valor pertenece a la clase y no cambia.La idea
Cuándo static y cuándo no
Pregúntate: «¿Esto pertenece a un objeto concreto?»
- Sí (el saldo de la cuenta, el nombre del producto, retirar de una cuenta): un campo o un método del objeto, sin
static. - No (el número de todas las cuentas, el IVA, la suma de dos números):
static.
Un error frecuente es hacerlo todo static, porque main es static y así el compilador deja de quejarse. Entonces todos los «objetos» comparten los mismos datos, y vuelves al problema de los arrays paralelos de la primera lección.
Ejercicio
Lo que comparten todos los objetos
Resuelve las tareas en tu editor.
1. Pedidos. Una clase Order con un cliente y un importe. Cada pedido nuevo recibe el número siguiente de un contador static. Un método static getCount() devuelve cuántos pedidos se han creado.
Order[] orders = { new Order("María", 45.5), new Order("Pablo", 120), new Order("Elena", 18.9)};for (Order o : orders) { System.out.println(o);}System.out.println("Pedidos: " + Order.getCount());Pedido n.º 1: María, 45.5 €Pedido n.º 2: Pablo, 120.0 €Pedido n.º 3: Elena, 18.9 €Pedidos: 32. Calculadora. Una clase Calculator con métodos static para sumar, restar y multiplicar números enteros, y para dividir números decimales.
System.out.println(Calculator.add(10, 20));System.out.println(Calculator.subtract(10, 15));System.out.println(Calculator.multiply(6, 7));System.out.println(Calculator.divide(5, 2));30-5422.53. Cambio de divisas. Una clase Converter con constantes para el tipo de cambio: 1 € = 1,08 dólares estadounidenses y 1 € = 0,85 libras esterlinas (los tipos son de ejemplo). Métodos static convierten un importe en euros a dólares y a libras. Para 20 €:
20.0 EUR = 21.6 USD20.0 EUR = 17.0 GBPMás tareas
- Cuentas numeradas. Añade a la
Accountde la lección «Encapsulamiento» un número de cuenta que sale de un contador static y un campofinalpara el número. - Cambio en los dos sentidos. Añade a
Convertermétodos que convierten de dólares y de libras a euros. Convierte 20 € a dólares y de vuelta a euros. ¿Obtienes exactamente 20? - Un límite. La tienda acepta como mucho 3 productos en un carrito. Añade una constante para eso, y haz que el método de añadir rechace el cuarto.
En resumen
El módulo en resumen
- Una clase es tu propio tipo, y un objeto es una instancia concreta de ella, creada con
new. La variable apunta al objeto. Clases y objetos - El constructor se asegura de que el objeto se cree completo.
thises el objeto actual. Constructores y this - Los métodos de instancia trabajan con los campos del objeto.
toString()dice cómo se ve como texto. Métodos de instancia privateoculta los campos. Desde fuera llegas a ellos solo a través de métodos que comprueban. Encapsulamientostatices lo que comparte la clase: contadores, métodos auxiliares y constantes.
Datos y reglas en un solo lugar que nadie puede saltarse: eso es la programación orientada a objetos. En el siguiente módulo, las clases empezarán a compartir código entre sí.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Clases y objetos · Constructores y this · Métodos de instancia · Encapsulamiento
- Continúa en: Principios de la POO · Lanzar excepciones y crear las tuyas · Expresiones regulares: grupos y reemplazo · Datos en archivos
- Ejercicios sobre el tema: Clases y objetos