Списък, който расте
Проблем
Количката е пълна
Пишеш количката на онлайн магазин и пазиш продуктите в масив. Масивът обаче има размер, който не се променя. Когато клиентът добави четвърти продукт, правиш по-голям масив и копираш всичко. Когато махне продукт, остава дупка:
String[] cart = { "Хляб", "Мляко", "Кафе" };
// Четвърти продукт: масивът е пълен, правим по-голям и копираме.String[] bigger = new String[cart.length + 1];for (int i = 0; i < cart.length; i++) { bigger[i] = cart[i];}bigger[cart.length] = "Масло";cart = bigger;
// Клиентът маха млякото.cart[1] = null;
for (String item : cart) { System.out.println(item);}ХлябnullКафеМаслоnull. А броят продукти вече не е cart.length.Това е много код за нещо, което всеки магазин прави постоянно.
Идеята
ArrayList: масив, който се грижи сам
Java има готов клас за това: ArrayList. Той пази елементите в масив, но копирането и местенето прави сам. Такава колекция се казва списък:
import java.util.ArrayList;
public class Main { public static void main(String[] args) { ArrayList<String> cart = new ArrayList<>(); cart.add("Хляб"); cart.add("Мляко"); cart.add("Кафе"); cart.add("Масло"); cart.remove("Мляко"); System.out.println(cart); System.out.println("Продукти: " + cart.size()); }}[Хляб, Кафе, Масло]Продукти: 3ArrayListе в пакетаjava.util, затова файлът започва сimport, както приScanner.addдобавя в края,removeмаха и затваря дупката: „Кафе“ се мести на мястото на „Мляко“.size()е колко елемента има в момента. Това не еlength, защото списъкът не е масив.printlnпечата целия списък в квадратни скоби.
Ето какво прави ArrayList вътре. Числото в скобите е началният размер на масива. Без него е 10, тук е 3, за да се види какво става, когато масивът се напълни:
ArrayList<String> cart = new ArrayList<>(3);cart.add("Хляб");cart.add("Мляко");cart.add("Кафе");cart.add("Масло");cart.remove("Мляко");System.out.println(cart);System.out.println("Продукти: " + cart.size());Стъпка по стъпка, с думи
- cart сочи обект ArrayList. Вътре има масив с 3 места и брояч size.
- add слага всеки елемент на първото свободно място и увеличава size.
- Масивът е пълен. ArrayList прави нов с 4 места и копира трите елемента в него.
- Обектът вече сочи новия масив, а старият не трябва на никого. „Масло“ влиза на свободното място.
- remove маха „Мляко“, а всичко след него се мести с едно място наляво. Дупка не остава.
- Отвън се вижда само списък с три продукта: копирането и местенето ги прави ArrayList.
Идеята
Какво пише в ъглите
ArrayList<String> значи „списък от текст“. Типът в ъглите се казва типов параметър (generics). Той казва на компилатора какво има в списъка, и компилаторът пази това:
import java.util.ArrayList;
public class Main { public static void main(String[] args) { ArrayList<String> cart = new ArrayList<>(); cart.add("Хляб"); cart.add(5); }}Main.java:7: error: incompatible types: int cannot be converted to String cart.add(5); ^Затова cart.get(0) връща направо String, без превръщане.
Отдясно ъглите са празни: new ArrayList<>(). Компилаторът взима типа от лявата страна.
Обикновено променливата се обявява с интерфейса List, а класът ArrayList стои само след new:
List<String> cart = new ArrayList<>();cart.add("Хляб");System.out.println(cart.get(0));Това е полиморфизмът от модул „Наследяване“: List казва какво може списъкът, а ArrayList е един начин да го прави. Нужни са два import-а: java.util.ArrayList и java.util.List. Оттук нататък урокът не ги показва.
Идеята
Индекси: get, set, add, indexOf
Списъкът има индекси като масива, от 0 до size() - 1. Само че вместо [ ] ползваш методи:
List<String> tasks = new ArrayList<>();tasks.add("Фактура за март");tasks.add("Обаждане до банката");tasks.add("Отчет за склада");
System.out.println(tasks.get(0));tasks.set(1, "Среща в банката");tasks.add(0, "Кафе с екипа");System.out.println(tasks);System.out.println(tasks.indexOf("Отчет за склада"));System.out.println(tasks.contains("Обаждане до банката"));Фактура за март[Кафе с екипа, Фактура за март, Среща в банката, Отчет за склада]3falseget(i)чете,set(i, x)заменя.add(i, x)вмъква на индексiи мести останалите надясно.indexOf(x)търси и връща индекса или-1, аcontains(x)казва само дали го има.
По-точно, за любопитните
- Индекс извън списъка спира програмата, както при масив:
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3containsиindexOfсравняват сequals, не с==. Затова намират текст, който е равен, а не само същия обект.- Вмъкването и махането отпред са бавни при голям списък: всички елементи след индекса се местят с едно място. За един милион елемента това е един милион премествания.
Пробвай
Кой е на ред?
Списъкът с чакащите при лекаря. Иван влиза, а Ана има час и се нарежда втора.
Какво ще отпечата програмата?
List<String> waiting = new ArrayList<>();waiting.add("Иван");waiting.add("Мария");waiting.add("Петър");waiting.remove(0);waiting.add(1, "Ана");System.out.println(waiting + " " + waiting.size());remove(0) маха Иван и Мария става първа, на индекс 0. add(1, "Ана") вмъква Ана на индекс 1 и бута Петър надясно. Остават трима.
Идеята
Обхождане
Списъкът се обхожда като масив: с for-each, когато ти трябват само елементите, или с индекс, когато ти трябва и номерът:
List<String> cart = new ArrayList<>();cart.add("Хляб");cart.add("Кафе");cart.add("Масло");
for (String item : cart) { System.out.println("- " + item);}for (int i = 0; i < cart.size(); i++) { System.out.println((i + 1) + ". " + cart.get(i));}- Хляб- Кафе- Масло1. Хляб2. Кафе3. МаслоПо-точно, за любопитните
- Не махай елементи от списъка, докато го обхождаш с for-each. Програмата спира:
for (String item : cart) { if (item.equals("Хляб")) { cart.remove(item); }}Exception in thread "main" java.util.ConcurrentModificationExceptionИзключението не идва винаги. Ако махнеш предпоследния елемент, цикълът спира по-рано и последният не се проверява. Затова не разчитай на него.
- Ако трябва да махаш, обхождай с индекс отзад напред. Така махането не мести елементите, които още не си проверил:
List<String> cart = new ArrayList<>();cart.add("Хляб");cart.add("Мляко");cart.add("Хляб");for (int i = cart.size() - 1; i >= 0; i--) { if (cart.get(i).equals("Хляб")) { cart.remove(i); }}System.out.println(cart);[Мляко]removeIf.Идеята
Числа в списък: Integer
Списък от оценки изглежда така, но не се компилира:
ArrayList<int> scores = new ArrayList<>();Main.java:5: error: unexpected type ArrayList<int> scores = new ArrayList<>(); ^ required: reference found: intВ ъглите трябва да има клас, а int не е клас. Затова всеки прост тип има клас-обвивка: Integer за int, Double за double, Boolean за boolean, Character за char.
List<Integer> scores = new ArrayList<>();scores.add(85);scores.add(64);scores.add(91);
int total = 0;for (int score : scores) { total += score;}System.out.println("Средно: " + (double) total / scores.size());Средно: 80.0scores.add(85) получава int, а списъкът иска Integer. Java сама го слага в обвивка. Това се казва опаковане (autoboxing). В цикъла int score е обратното, разопаковане. Почти винаги не забелязваш разликата.
По-точно, за любопитните
- Сравнявай
Integerсequals, не с==.==проверява дали двете променливи сочат един и същ обект:
Integer a = 127, b = 127;Integer c = 128, d = 128;System.out.println(a == b);System.out.println(c == d);System.out.println(c.equals(d));truefalsetrueJava пази готови обекти за числата от -128 до 127, затова при 127 двете стрелки сочат един обект. При 128 обектите са два.
Integerможе да еnull, аintне може. Ако разопаковашnull, програмата спира сNullPointerException. Ще го срещнеш в урока заHashMap.
Пробвай
Намери грешката
Залата пази заетите места в списък. Клиентът се отказва от място 2 и програмата трябва да отпечата [12, 7, 5]. Вместо това печата [12, 2, 5]. На кой ред е грешката?
Избери реда с грешката.
List има два метода remove: remove(int index) и remove(Object o). При remove(2) аргументът е int, затова се вика първият и маха елемента на индекс 2, тоест мястото 7. За да махнеш стойността 2, дай обект: seats.remove(Integer.valueOf(2)). При List<String> този капан го няма, защото текстът никога не е индекс.Идеята
Подреждане и готов списък
Collections.sort подрежда списъка на място: текста по азбучен ред, числата по големина. Класът Collections също е в java.util.
List<String> cities = new ArrayList<>();cities.add("София");cities.add("Пловдив");cities.add("Варна");Collections.sort(cities);System.out.println(cities);
List<Integer> prices = new ArrayList<>(List.of(30, 5, 12));Collections.sort(prices);System.out.println(prices);[Варна, Пловдив, София][5, 12, 30]List.of(30, 5, 12) прави готов списък от изброените стойности. Той обаче е неизменим: не можеш да добавяш, махаш или подреждаш в него.
List<String> days = List.of("пн", "вт", "ср");days.add("чт");Exception in thread "main" java.lang.UnsupportedOperationExceptionЗатова по-горе цените са копирани в нов ArrayList: new ArrayList<>(List.of(30, 5, 12)) е списък, който можеш да променяш.
По-точно, за любопитните
- Текстът се подрежда по кодовете на символите. Главните букви са преди малките, затова „Ябълка“ е преди „ананас“:
List<String> fruit = new ArrayList<>(List.of("ананас", "Ябълка", "банан", "Круша"));Collections.sort(fruit);System.out.println(fruit);[Круша, Ябълка, ананас, банан]- Как се подреждат твои обекти, например продукти по цена, ще видиш в урока „Класове в класове“.
Упражнение
Списъци в класове
private поле на класа, а main вика само методите му.1. Рафт с книги. Клас BookShelf с методи addBook(String title), който слага книгата в края, и takeBook(), който връща и маха първата книга (или null, ако рафтът е празен). Добави count() и toString(). В main сложи „Под игото“, „Тютюн“ и „Железният светилник“, вземи една книга и отпечатай останалите.
Взе: Под иготоОстават 2: [Тютюн, Железният светилник]2. Журнал на събитията. Клас Event с дата и описание и клас EventLog с методи addEvent(String date, String description) и getEventsBetween(String from, String to), който връща списък със събитията между двете дати, включително. Датите са текст във вида 2026-03-31. В този вид текстът се подрежда като датите, затова compareTo от урока за текста ги сравнява вярно. Добави четири събития: 2026-01-15 „Годишна инвентаризация“, 2026-03-31 „Отчет за първото тримесечие“, 2026-05-20 „Нов доставчик на кафе“, 2026-09-01 „Нов учебен сезон“. Отпечатай тези от 2026-03-01 до 2026-06-30.
2026-03-31 Отчет за първото тримесечие2026-05-20 Нов доставчик на кафе3. Тетрадка. Клас Note със заглавие и съдържание и клас Notebook с методи addNote, findNote(String title) (връща бележката или null), deleteNote(String title) (връща true, ако е изтрил) и count(). Добави бележки „ООП“, „Колекции“ и „SQL“, намери „Колекции“, изтрий „ООП“ и провери, че вече я няма.
Колекции: ArrayList расте самИзтрита: ООПБележки: 2Няма бележка ООП.4. Смятане наум. Клас Question с текст и верен отговор и клас MathGame с списък от въпроси, метод addQuestion и метод start(Scanner in). start задава въпросите един по един, чете отговора и накрая казва колко са верните. Въпросите са 2 + 2, 3 * 4 и 15 - 9.
41272 + 2 = Вярно!3 * 4 = Вярно!15 - 9 = Грешно, отговорът е 6.Резултат: 2 от 3Още задачи
- Без повторения. Прочети 6 имена и ги събери в списък, като пропускаш тези, които вече са в него. При вход Иван, Мария, Иван, Ана, Мария, Петър изходът е
[Иван, Мария, Ана, Петър]. - Най-скъпият продукт. В списък List<Double> има цени. Намери най-високата и на кой индекс е, без
Collections. - Обърни списъка. Напиши метод, който връща нов списък със същите елементи в обратен ред. Не променяй първоначалния.
- Списък за пазаруване. Чети команди
add мляко,remove мляко,listиendи пази списъка между тях. Командата за триене на продукт, който го няма, казва „Няма такъв продукт.“
Тест