Тема 2 — Референтни типове. Масиви. String. Базов клас Object.
Един от ключовите концептуални преходи при усвояването на Java е разбирането на разликата между прости и референтни типове. В C++ масивите и низовете са синтактична захар около прости указатели; в Java те са пълноправни обекти с методи, гаранции за безопасност и управление от GC (Garbage Collector). Тази глава разглежда масивите, системните класове за работа с низове и базовия клас Object, от който наследява всеки клас в езика.
1. Масиви (Array)
1.1 Организация и деклариране
В Java декларацията на масив създава само референция — паметта за елементите се заделя с new:
// C++:int A[10]; // алокира памет веднага
// Java:int[] A; // само референция (стойност null)A = new int[10]; // алокира масив в heapA[0] = 5;Масивите са референтен тип, наследник на Object. При алокация елементите получават нулеви стойности автоматично:
| Тип на елементите | Нулева стойност |
|---|---|
| числови типове | 0 |
boolean | false |
char | ' ' |
| референции | null |
1.2 Инициализация
int[] primes = { 2, 3, 5, 7, 11 }; // инициализаторbyte[] bar = new byte[] { 1, 2, 3, 4, 5 }; // алтернативен синтаксис1.3 Граничен контрол и length
Java проверява всеки индекс автоматично. При излизане извън границите се хвърля ArrayIndexOutOfBoundsException. Полето length е достъпно за всеки масив без допълнителна декларация:
int[] A = new int[10];System.out.println(A.length); // 10A = new int[20];System.out.println(A.length); // 20 — length следва масива, не е константа1.4 Многомерни и нееднородни масиви
int[][] матрица = new int[4][3]; // редовни 4×3
// Нееднородни редове (jagged arrays):int[][] jagged = new int[4][];jagged[1] = new int[4];jagged[3] = new int[2];
// Двумерен масив от обекти:Object[][] board = new Object[2][3];board[0][0] = new Rectangle(0, 1, 2, 3);1.5 Копиране — System.arraycopy
Простото присвояване B = A копира само референцията, не данните. За реално копиране се ползва:
System.arraycopy(src, srcPos, dst, dstPos, count);
// Пример — копира 5 елемента от A[0..4] в B[0..4]:int[] A = new int[10];int[] B = new int[5];System.arraycopy(A, 0, B, 0, 5);Особености:
- При прости типове — задължително типово съответствие между
srcиdst; - При референтни типове — позволено само ако присвояването
B[i] = A[i]е валидно; при несъответствие —ArrayStoreExceptionпо времена на изпълнение; - Работи коректно при съвпадение на source и destination (вътрешно копиране).
2. Капани с референции на масиви
Тъй като масивите са референтни типове, присвояването копира референцията — двете имена сочат към един и същ обект в heap:
int[] A = new int[]{5, 0, 0};int[] B = A; // B и A сочат едно и също мястоB[0] = 2;// A[0] == 2 !По-деликатен проблем — ArrayStoreException:
String[] strings = new String[10];Object[] objects = strings; // String[] е подтип на Object[] — допустимоobjects[0] = new Date(); // ArrayStoreException при изпълнение! // реалният масив е String[], Date не пасва3. Низове — String, StringBuilder, StringBuffer
Java предоставя два различни класа за работа с низове, с коренно различна семантика:
| Клас | Характеристика | Кога се използва |
|---|---|---|
String | Непроменяем (immutable) | По подразбиране; безопасно предаване |
StringBuilder | Променяем, несинхронизиран | Изграждане в рамките на метод |
StringBuffer | Променяем, синхронизиран | Споделен между нишки |
3.1 Клас String
String е final клас — не може да се наследява. Вътрешно представяне: char[] с final маркировка, което прави обектите неизменяеми след създаването им.
Конструиране:
String quote = "constant string"; // от литерал (string pool)String characters = new String(charArray); // от char[]String encoded = new String(data, "ISO8859_1");Основни операции:
quote.length() // брой символи"John " + "Smith" // конкатенация с +"John ".concat("Smith") // еквивалентноs.charAt(index) // символ на позицияurl.startsWith("http:") // проверка на префиксabcs.indexOf('p') // индекс на символ (-1 ако не е намерен)abcs.indexOf("def") // индекс на поднизкаlog.contains("emergency") // (Java 5.0+)str.trim() // премахване на whitespace"FOO".toLowerCase()"abcde".substring(2, 5) // "cde""xxooxxxoo".replace("xx", "X")Сравняване — задължително с equals(), никога с ==:
String one = "FOO";String two = "foo";one.equals(two); // false — правилно!one.equalsIgnoreCase(two); // trueone == two; // ГРЕШНО — сравнява референции, не стойности== проверява дали двете референции сочат един и същ обект в паметта. Два отделни new String("FOO") извиквания създават различни обекти с == == false, дори ако съдържанието е идентично.
Автоматичен StringBuilder при конкатенация:
Компилаторът оптимизира верига от + операции до StringBuilder:
String foo = "To " + "be " + "or";// Еквивалентно на:String foo = new StringBuilder().append("To ").append("be ").append("or").toString();3.2 StringBuilder и StringBuffer
За многократно изграждане на низ в цикъл задължително се ползва StringBuilder — String + в цикъл реалокира нов обект при всяка итерация:
// ГРЕШНО — O(n²) реалокации:String text = "";while ((line = readLine()) != null) text += line;
// ПРАВИЛНО — единична алокация с нарастване:StringBuilder sb = new StringBuilder();while ((line = readLine()) != null) sb.append(line);String text = sb.toString();Специфични методи: append(), insert(), reverse() — предефинирани за различни типове.
4. Базов клас Object
Всеки клас в Java неявно наследява Object. Дефинираните методи образуват минимален договор, задължителен за всеки обект:
| Метод | Описание |
|---|---|
protected Object clone() | Плитко копие на обекта |
boolean equals(Object o) | Сравнение по стойност |
protected void finalize() | Извиква се от GC при нулиране на всички референции |
final Class getClass() | Клас по времена на изпълнение |
int hashCode() | Хеш код за HashMap, HashSet |
String toString() | Низово представяне |
4.1 clone() — плитко и дълбоко копиране
- Плитко (shallow clone) — копира само полетата; ако поле е референция, двата обекта сочат един и същ вътрешен обект;
- Дълбоко (deep clone) — рекурсивно копира и съдържанието на референтните полета.
4.2 getClass()
Object obj = new ArrayList();obj.getClass().getName(); // "java.util.ArrayList"4.3 hashCode()
Гарантирано е консистентен по времена на изпълнение на програмата. Използва се от HashMap и HashSet за намиране на кофата (bucket). Правило: ако equals() е предефиниран, hashCode() трябва да е предефиниран по съвместим начин — равни обекти трябва да дават равни хеш кодове.
public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + ((strName == null) ? 0 : strName.hashCode()); result = PRIME * result + ((strTitle == null) ? 0 : strTitle.hashCode()); return result;}4.4 equals()
Еквивалент на operator== на C++ при сравнение на съдържание. Трябва да отговаря на:
- Рефлексивност:
x.equals(x)==true; - Симетричност:
x.equals(y)⟺y.equals(x); - Транзитивност:
x.equals(y)&&y.equals(z)⟹x.equals(z); - Консистентност при непроменени обекти;
x.equals(null)==falseвинаги.
public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Student other = (Student) obj; return strFacNumer != null && strFacNumer.equals(other.strFacNumer);}4.5 toString()
Предефинирането позволява конзолен изход, сериализация и предаване в поток:
public String toString() { return strTitle + " " + strName + "\tUpdated: " + lastDate;}5. Видове класове по обхват
- На ниво файл — стандартен публичен клас;
- На ниво пакет — без
public, видим само в пакета; - Вградени (Inner Classes) — в обхват
{}:- в клас (Inner class) — достъп до полетата на обхващащия;
- в метод (локален) — видим само в метода;
- анонимен — без явно зададено име, деклариран inline.
Резюме
- Масивите в Java са референтни типове; декларацията не алокира памет — нужно е
new. При излизане от индекс —ArrayIndexOutOfBoundsException. - Присвояването
B = Aкопира само референцията; за реално копиране се ползваSystem.arraycopy(). Stringе непроменяем; за изграждане в цикъл задължително се ползваStringBuilder.- Сравняването на
Stringобекти се прави сequals(), а не с==. - Базовият клас
Objectдефинираclone(),equals(),hashCode(),toString(),getClass()— при предефиниране наequals()трябва да се предефинира иhashCode().