static: общото за всички
Проблем
Всеки продукт е №1
Магазинът иска всеки нов продукт да получава пореден номер: 1, 2, 3… Слагаш брояч nextId в класа. Конструкторът дава на продукта текущия номер и увеличава брояча:
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 "№" + id + " " + name; }}
Product bread = new Product("Хляб");Product milk = new Product("Мляко");Product coffee = new Product("Кафе");System.out.println(bread);System.out.println(milk);System.out.println(coffee);№1 Хляб№1 Мляко№1 КафеВсички са №1. Причината е, че nextId е поле на обекта, както името. Всеки нов продукт получава свой брояч, който започва от 1. Броячът на хляба никога не стига до млякото.
Идеята
Поле на класа, не на обекта
Броячът не е част от един продукт. Той е общ за всички продукти. Добави static:
private static int nextId = 1;static поле е поле на класа. То е едно, колкото и обекти да създадеш, и всички обекти го делят:
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 "№" + id + " " + name; }}
public class Main { public static void main(String[] args) { Product bread = new Product("Хляб"); Product milk = new Product("Мляко"); Product coffee = new Product("Кафе"); System.out.println(bread); System.out.println(milk); System.out.println(coffee); }}№1 Хляб№2 Мляко№3 КафеХлябът взима 1 и прави брояча 2. Млякото вижда същия брояч, взима 2 и го прави 3.
private static int nextId = 1;
public Product(String name) { this.name = name; id = nextId; nextId++;}
Product bread = new Product("Хляб");Product milk = new Product("Мляко");Product coffee = new Product("Кафе");Стъпка по стъпка, с думи
- nextId е static: едно поле в класа, а не във всеки обект.
- new Product("Хляб"): нов обект, конструкторът записва името.
- id = nextId: обектът взима текущия номер, 1.
- nextId++ сменя общия брояч: вече е 2.
- Мляко: същият брояч. Взима 2 и го прави 3.
- Кафе: взима 3 и го прави 4.
- Броячът е един за всички, затова номерата са поредни.
Ако полето е public static, отвън до него се стига през името на класа (Product.nextId), а не през обект.
Пробвай
Общо и свое
Какво ще отпечата програмата?
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 е static: един брояч за всички поръчки, увеличен три пъти. items е поле на обекта: всяка поръчка има свое, увеличено веднъж.Идеята
static метод: работа без обект
Същото важи за методите. static метод е метод на класа. Вика се през името на класа, без 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.258На Calculator.add не му трябва обект: всичко, от което има нужда, идва като параметри. Такива са и методите на Math: Math.max, Math.sqrt. Никой не пише new Math().
Сега е ясно защо main е static. Когато програмата тръгва, обекти още няма. Средата вика main направо през класа. Затова и методите от модул „Методи“, които викахме от main, бяха static.
По-точно, за любопитните
Обратното също работи: в static метод можеш да създадеш обект и да викаш неговите методи. Точно това правим в main от началото на модула.
Пробвай
Намери грешката
Избери реда с грешката.
label() чете name, а name е на конкретен продукт. static метод няма обект, затова не знае чие име да вземе:error: non-static variable name cannot be referenced from a static contextstatic: етикетът е на продукта, затова е метод на обекта.Идеята
Константи
ДДС е 20% за всички продукти и не се променя, докато програмата работи. Такава стойност е константа:
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("ДДС: " + Prices.VAT_RATE * 100 + "%");60.0ДДС: 20.0%static final значи „на класа“ и „не може да се промени“. Имената на константите в Java са с главни букви и долна черта: VAT_RATE.
Ако някой опита да я смени,
Prices.VAT_RATE = 0.25;програмата не се компилира:
error: cannot assign a value to static final variable VAT_RATEПолзата е двойна: числото 0.20 е написано на едно място с ясно име, и никой не може да го промени по погрешка.
По-точно, за любопитните
Понякога стойността е различна за всеки обект, но след създаването не трябва да се мени. Такъв е номерът на продукта:
private static int nextId = 1;private final int id;
public Product(String name) { this.name = name; id = nextId; nextId++;}final поле (без static) се задава веднъж, в конструктора, и после не се сменя. Опит за id = 5; в метод не се компилира.
Пробвай
Кое липсва?
class Calendar {
public static int MONTHS = 12;
}final: с static final стойността е на класа и не се сменя.Идеята
Кога static и кога не
Питай се: „Това на конкретен обект ли е?“
- Да (балансът на сметката, името на продукта, тегленето от сметка): поле или метод на обекта, без
static. - Не (броят на всички сметки, ДДС, сбор на две числа):
static.
Честа грешка е всичко да стане static, защото main е static и компилаторът спира да се оплаква. Тогава всички „обекти“ делят едни и същи данни и се връщаш при проблема с успоредните масиви от първия урок.
Упражнение
Общото за всички
Реши задачите в редактора си.
1. Поръчки. Клас Order с клиент и сума. Всяка нова поръчка получава пореден номер от static брояч. Static метод getCount() връща колко поръчки са създадени.
Order[] orders = { new Order("Мария", 45.5), new Order("Петър", 120), new Order("Елена", 18.9)};for (Order o : orders) { System.out.println(o);}System.out.println("Поръчки: " + Order.getCount());Поръчка №1: Мария, 45.5 €Поръчка №2: Петър, 120.0 €Поръчка №3: Елена, 18.9 €Поръчки: 32. Калкулатор. Клас Calculator със static методи за събиране, изваждане и умножение на цели числа и за деление на дробни.
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. Обмяна. Клас Converter с константи за курса: 1 € = 1,08 щатски долара и 1 € = 0,85 британски лири (курсовете са примерни). Static методи превръщат сума в евро в долари и в лири. За 20 €:
20.0 EUR = 21.6 USD20.0 EUR = 17.0 GBPОще задачи
- Сметки с номера. Към
Accountот урока „Капсулация“ добави пореден номер на сметката от static брояч иfinalполе за номера. - Обмяна в двете посоки. Добави към
Converterметоди от долари и от лири към евро. Превърни 20 € в долари и обратно в евро. Получава ли се точно 20? - Ограничение. Магазинът приема най-много 3 продукта в количка. Сложи константа за това и накарай метода за добавяне да отказва четвъртия.
Накратко
Модулът накратко
- Клас е твой тип, обект е конкретен екземпляр по него, създаден с
new. Променливата сочи към обекта. Свой тип - Конструкторът задължава обектът да се създаде цял.
thisе текущият обект. Конструктори и this - Методите на обекта работят с неговите полета.
toString()казва как изглежда като текст. Поведение privateкрие полетата. Отвън се стига до тях само през методи, които проверяват. Капсулацияstaticе общото за класа: броячи, помощни методи и константи.
Данни и правила на едно място, които никой не може да заобиколи: това е обектно-ориентираното програмиране. В следващия модул класовете ще започнат да споделят код помежду си.
Тест