Към съдържанието
wannacode

Списък, който расте

Проблем

Количката е пълна

Пишеш количката на онлайн магазин и пазиш продуктите в масив. Масивът обаче има размер, който не се променя. Когато клиентът добави четвърти продукт, правиш по-голям масив и копираш всичко. Когато махне продукт, остава дупка:

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. Той пази елементите в масив, но копирането и местенето прави сам. Такава колекция се казва списък:

Main.java
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());
}
}
Изход
[Хляб, Кафе, Масло]
Продукти: 3
  • ArrayList е в пакета 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());
Стъпка по стъпка, с думи
  1. cart сочи обект ArrayList. Вътре има масив с 3 места и брояч size.
  2. add слага всеки елемент на първото свободно място и увеличава size.
  3. Масивът е пълен. ArrayList прави нов с 4 места и копира трите елемента в него.
  4. Обектът вече сочи новия масив, а старият не трябва на никого. „Масло“ влиза на свободното място.
  5. remove маха „Мляко“, а всичко след него се мести с едно място наляво. Дупка не остава.
  6. Отвън се вижда само списък с три продукта: копирането и местенето ги прави ArrayList.

Идеята

Какво пише в ъглите

ArrayList<String> значи „списък от текст“. Типът в ъглите се казва типов параметър (generics). Той казва на компилатора какво има в списъка, и компилаторът пази това:

Main.java
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("Обаждане до банката"));
Изход
Фактура за март
[Кафе с екипа, Фактура за март, Среща в банката, Отчет за склада]
3
false
  • get(i) чете, set(i, x) заменя.
  • add(i, x) вмъква на индекс i и мести останалите надясно.
  • indexOf(x) търси и връща индекса или -1, а contains(x) казва само дали го има.
По-точно, за любопитните
  • Индекс извън списъка спира програмата, както при масив:
tasks.get(3) при 3 задачи
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
  • contains и 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());
Отговор

Идеята

Обхождане

Списъкът се обхожда като масив: с 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);
Изход
[Мляко]
В модул „Модерна Java“ ще видиш по-кратък начин: 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.0

scores.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));
Изход
true
false
true

Java пази готови обекти за числата от -128 до 127, затова при 127 двете стрелки сочат един обект. При 128 обектите са два.

  • Integer може да е null, а int не може. Ако разопаковаш null, програмата спира с NullPointerException. Ще го срещнеш в урока за HashMap.

Пробвай

Намери грешката

Залата пази заетите места в списък. Клиентът се отказва от място 2 и програмата трябва да отпечата [12, 7, 5]. Вместо това печата [12, 2, 5]. На кой ред е грешката?

Избери реда с грешката.

Идеята

Подреждане и готов списък

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.

Вход
4
12
7
Изход
2 + 2 = Вярно!
3 * 4 = Вярно!
15 - 9 = Грешно, отговорът е 6.
Резултат: 2 от 3

Още задачи

  • Без повторения. Прочети 6 имена и ги събери в списък, като пропускаш тези, които вече са в него. При вход Иван, Мария, Иван, Ана, Мария, Петър изходът е [Иван, Мария, Ана, Петър].
  • Най-скъпият продукт. В списък List<Double> има цени. Намери най-високата и на кой индекс е, без Collections.
  • Обърни списъка. Напиши метод, който връща нов списък със същите елементи в обратен ред. Не променяй първоначалния.
  • Списък за пазаруване. Чети команди add мляко, remove мляко, list и end и пази списъка между тях. Командата за триене на продукт, който го няма, казва „Няма такъв продукт.“

Тест

Провери се

1. С какво ArrayList е по-удобен от масив за количка?
2. Какво казва <String> в List<String> cart?
3. List<Integer> seats съдържа [12, 2, 7, 5]. Какво остава след seats.remove(2)?
4. Защо не може ArrayList<int>?
5. List<String> days = List.of("пн", "вт"); Какво става при days.add("ср")?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·