Savladajte Java nizove: veoma specijalan objekat
„Erge, video sam u jednom članku na tvom javnom nalogu da je ArrayList interno implementiran pomoću nizova, pa sam postao veoma zainteresovan za nizove i želim da ih detaljno upoznam. Konačno smo stigli do te teme, jedva čekam!“ rekla je Sanmej uzbuđeno.
„Zaista je tako, pogledaj izvorni kod ArrayList-a i biće ti jasno.“ rekao sam, istovremeno otvarajući Intellij IDEA i našavši izvorni kod ArrayList-a.
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;„Vidiš? Object[] elementData je upravo niz.“ rekao sam pokazujući na ovaj kod na ekranu.
Niz je objekat koji sadrži fiksni broj elemenata, i svi ti elementi su istog tipa. Niz elemente smešta na određene pozicije po indeksu, što znači da im možemo pristupiti preko indeksa. U Javi, indeksi počinju od 0.
„Brate, možeš li da mi kažeš zašto indeksi počinju od 0?“ Sanmej je iznenada postala veoma zainteresovana za ovu temu.
„Pa, Java je implementirana na osnovu C/C++ jezika, a u C jeziku indeksi počinju od 0, tako da je Java nasledila ovu dobru tradiciju (to sam izmislio). C jezik ima jedan važan koncept koji se zove pokazivač, koji je zapravo ofset — rastojanje od početne pozicije. Prvi element se nalazi na početnoj poziciji, pa je njegov ofset 0, zato je i indeks 0.“ U tom trenutku sam bio veoma samouveren.
„Pored toga, postoji još jedno objašnjenje. U ranim danima računarski resursi su bili veoma oskudni, i korišćenje 0 kao početnog indeksa u poređenju sa 1 je bilo efikasnije pri kompilaciji.“
„Aha.“ Sanmej je klimnula glavom sa značenjem.
Možemo zamisliti niz kao niz uredno poređanih ćelija, gde svaka ćelija sadrži jedan element.
Na primer, niz na slici ispod ima vrednosti [a,b,c,a,b,c,b,b], sa indeksima redom 0, 1, 2, 3, 4, 5, 6, 7.

Tip elemenata niza može biti primitivni tip (na primer int, double), ili referentni tip (na primer String), uključujući korisnički definisane tipove.
Deklaracija i inicijalizacija niza
Postoje dva načina za deklaraciju niza.
Pogledajmo prvi način:
int[] anArray;Sada pogledajmo drugi način:
int anOtherArray[];Razlika je u poziciji uglastih zagrada — da li slede iza ključne reči za tip ili iza imena promenljive. Prvi način se češće koristi, na primer u izvornom kodu ArrayList-a se koristi prvi način.
Slično tome, postoji više načina za inicijalizaciju niza, a najčešći je:
int[] anArray = new int[10];Vidiš? Gorenavedeni kod koristi ključnu reč new, što znači da je niz zaista objekat — samo se za kreiranje objekata koristi ključna reč new, dok se primitivni tipovi podataka ne kreiraju sa new (omotačke klase primitivnih tipova mogu da se kreiraju sa new, jer su omotačke klase objekti). Zatim, u uglastim zagradama moramo navesti dužinu niza.
U tom trenutku, svaki element niza se inicijalizuje na podrazumevanu vrednost — za tip int to je 0, a za tip Object to je null. Podrazumevane vrednosti se razlikuju za različite tipove podataka, što možete pogledati u ranijem članku.
Pored toga, možete koristiti vitičaste zagrade da direktno inicijalizujete elemente niza:
int anOtherArray[] = new int[] {1, 2, 3, 4, 5};Sada su elementi niza redom 1, 2, 3, 4, 5, sa indeksima redom 0, 1, 2, 3, 4, a dužina je 5.
Najčešće operacije nad nizovima
„Brate, kako se pristupa elementima niza?“ Sanmej je pravovremeno pitala.
Kao što je ranije pomenuto, elementima niza se može pristupiti preko indeksa, ovako:
anArray[0] = 10;Ime promenljive, plus uglaste zagrade, plus indeks elementa — i tako se pristupa nizu; pomoću operatora = možemo dodeliti vrednost elementu.
Ako vrednost indeksa prelazi granice niza, biće bačen izuzetak ArrayIndexOutOfBoundException. Pošto indeksi niza počinju od 0, maksimalni indeks je length - 1 — nemojte koristiti indekse van ovog opsega, jer će u protivnom biti bačen izuzetak prelaska granica niza.
Na primer, ako deklarišete niz veličine 10 i pokušate da pristupite elementu sa indeksom 10, dobićete ovaj izuzetak. Pošto indeksi niza počinju od 0, indeks poslednjeg elementa je length - 1, odnosno 9.
Kada niz ima veoma mnogo elemenata, pristupanje pojedinačno je previše naporno, pa je potrebno koristiti iteraciju.
Prvi način, korišćenjem for petlje:
int anOtherArray[] = new int[] {1, 2, 3, 4, 5};
for (int i = 0; i < anOtherArray.length; i++) {
System.out.println(anOtherArray[i]);
}Preko svojstva length dobijamo dužinu niza, a zatim iteriramo od 0 i tako dobijamo sve elemente niza.
Drugi način, korišćenjem for-each petlje:
for (int element : anOtherArray) {
System.out.println(element);
}Ako ne morate da vodite računa o indeksu (što znači da ne morate da menjate neki element niza), for-each je koncizniji način iteracije. Naravno, možete koristiti i while i do-while petlje.
Promenljivi broj argumenata i nizovi
U Javi, promenljivi broj argumenata (varargs) služi za prosleđivanje proizvoljnog broja argumenata metodi. Pogledajmo metod varargsMethod():
void varargsMethod(String... varargs) {}Ovaj metod može primiti proizvoljan broj string argumenata — nijedan, jedan ili N. U suštini, promenljivi broj argumenata je implementiran pomoću nizova. Da bismo ovo dokazali, pogledajmo bajtkod nakon dekompilacije:
public class VarargsDemo
{
public VarargsDemo()
{
}
transient void varargsMethod(String as[])
{
}
}Dakle, zapravo možemo direktno proslediti niz kao argument ovom metodu:
VarargsDemo demo = new VarargsDemo();
String[] anArray = new String[] {"Chenmo Wang Er", "zanimljiv programer"};
demo.varargsMethod(anArray);Možemo i direktno proslediti više stringova, razdvojenih zarezima:
demo.varargsMethod("Chenmo Wang Er", "zanimljiv programer");Nizovi i List
U Javi, nizovi i List su veoma usko povezani. List enkapsulira mnoge često korišćene metode koje olakšavaju operacije nad kolekcijama, dok je direktan rad sa nizovima u mnogome nezgodan jer niz sam po sebi ne pruža ove enkapsulirane operacije, pa ponekad moramo pretvoriti niz u List.
List će biti detaljno predstavljen u odeljku o okviru kolekcija; ovde dajemo samo kratki uvod, kako biste lakše ponovili gradivo kasnije.
„Kako se vrši konverzija?“ pitala je Sanmej.
Najosnovniji način je da se iteracijom kroz niz element po element doda u List.
int[] anArray = new int[] {1, 2, 3, 4, 5};
List<Integer> aList = new ArrayList<>();
for (int element : anArray) {
aList.add(element);
}Elegantniji način je preko metoda asList() klase Arrays (kliknite na link za detalje):
List<Integer> aList = Arrays.asList(anArray);Međutim, treba imati na umu da Arrays.asList kao argument očekuje niz tipa Integer, dok je anArray trenutno tipa int.
Možete napisati ovako:
List<Integer> aList1 = Arrays.asList(1, 2, 3, 4, 5);Ili koristiti drugi pristup:
List<Integer> aList = Arrays.stream(anArray).boxed().collect(Collectors.toList());Ovo se ponovo dotiče znanja o Java tokovima (Stream) — kliknite na link za detalje.
Još jedna stvar na koju treba obratiti pažnju: ArrayList koji vraća metod Arrays.asList nije java.util.ArrayList, već je zapravo interna klasa klase Arrays:
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable{}Ako su potrebni dodavanje ili brisanje elemenata, morate ga pretvoriti u java.util.ArrayList.
new ArrayList<>(Arrays.asList(anArray));Java 8 je uvela koncept Stream tokova, što znači da niz možemo pretvoriti u Stream i nad njim vršiti operacije.
String[] anArray = new String[] {"Chenmo Wang Er", "zanimljiv programer", "čuvajte ga pažljivo"};
Stream<String> aStream = Arrays.stream(anArray);Sortiranje i pretraga nizova
Ako želite da sortirate niz, možete koristiti metod sort() klase Arrays.
- Primitivni tipovi podataka se sortiraju rastuće
- Objekti koji implementiraju interfejs Comparable se sortiraju prema
compareTo()
Pogledajmo prvi primer:
int[] anArray = new int[] {5, 2, 1, 4, 8};
Arrays.sort(anArray);Rezultat sortiranja je prikazan ispod:
[1, 2, 4, 5, 8]Pogledajmo drugi primer:
String[] yetAnotherArray = new String[] {"A", "E", "Z", "B", "C"};
Arrays.sort(yetAnotherArray, 1, 3,
Comparator.comparing(String::toString).reversed());Ovim se samo elementi na pozicijama 1-3 sortiraju obrnutim redosledom, pa je rezultat:
[A, Z, E, B, C]Ponekad nam je potrebno da pronađemo određeni element u nizu, a najdirektniji način je iteracija:
int[] anArray = new int[] {5, 2, 1, 4, 8};
for (int i = 0; i < anArray.length; i++) {
if (anArray[i] == 4) {
System.out.println("pronađeno " + i);
break;
}
}U gorenjem primeru tražimo element 4 u nizu, a kada ga pronađemo izlazimo iz petlje pomoću ključne reči break.
Ako je niz prethodno sortiran, možemo koristiti binarnu pretragu, što je znatno efikasnije. Metod Arrays.binarySearch() nam je na raspolaganju — potrebno je proslediti niz i element koji tražimo.
int[] anArray = new int[] {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(anArray, 4);„Pored jednodimenzionalnih nizova, postoje i dvodimenzionalni nizovi, Sanmej možeš da ih proučiš — na primer, pokušaj da isprintaš Paskalov trougao pomoću dvodimenzionalnog niza. O tome će biti reči u sledećem odeljku.“
Kopiranje nizova
Ponekad nam je potrebno da kopiramo vrednosti iz jednog niza u drugi, što uključuje znanje o kopiranju nizova.
U klasi String (koja se predaje nakon nizova) ćete zapravo često sresti kopiranje nizova, na primer u metodi substring().
public String substring(int beginIndex) {
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}Obratite pažnju na new String() — on vraća novi string čija je vrednost deo originalnog stringa, a ovaj proces uključuje kopiranje niza.
public String(char value[], int offset, int count) {
this.value = Arrays.copyOfRange(value, offset, offset+count);
}Metod Arrays.copyOfRange() služi upravo za kopiranje nizova, i biće objašnjen kada budemo govorili o klasi Arrays.
On interno poziva metod System.arraycopy(), koji je nativni metod — implementiran u C/C++-u i veoma efikasan.

Definicija metoda System.arraycopy je prikazana ispod:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);Upotreba je prikazana ispod:
int[] array1 = {1, 2, 3};
int[] array2 = {4, 5, 6};
// Kreira novi niz čija je dužina zbir dužina dva niza
int[] mergedArray = new int[array1.length + array2.length];
// Kopira prvi niz u novi niz
System.arraycopy(array1, 0, mergedArray, 0, array1.length);
System.out.println(Arrays.toString(mergedArray));
// Kopira drugi niz u novi niz
System.arraycopy(array2, 0, mergedArray, array1.length, array2.length);
System.out.println(Arrays.toString(mergedArray));Izlaz je prikazan ispod:
[1, 2, 3, 0, 0, 0]
[1, 2, 3, 4, 5, 6]Naravno, možemo koristiti i petlju za kopiranje niza:
int[] array1 = {1, 2, 3};
int[] array2 = {4, 5, 6};
// Kreira novi niz čija je dužina zbir dužina dva niza
int[] mergedArray = new int[array1.length + array2.length];
// Kopira prvi niz u novi niz
int index = 0;
for (int element : array1) {
mergedArray[index++] = element;
}
// Kopira drugi niz u novi niz
for (int element : array2) {
mergedArray[index++] = element;
}Veoma je jednostavno i lako za razumevanje, verujem da i ti, Sanmej, možeš da razumeš.
Prelazak granica niza
Kada radimo operacije nad nizovima, jedan od najčešćih problema na koji nailazimo je prelazak granica niza, odnosno izuzetak ArrayIndexOutOfBoundsException.
int[] anArray = new int[] {1, 2, 3, 4, 5};
System.out.println(anArray[5]);Gorenji kod će baciti izuzetak prelaska granica niza, jer indeksi niza počinju od 0, pa je maksimalni indeks length - 1, odnosno 4 — kada koristimo 5 kao indeks, biće bačen izuzetak.
Zato obratite pažnju na opseg indeksa pre nego što izvršite operacije nad nizom.
Rezime
Dobro, danas ćemo se zaustaviti ovde. Rekavši to, otrčao sam na balkon da zapalim jednu, ostavivši Sanmej ispred računara da vežba.
