Gyakorlati alapok III.
Lista (List)
Ismételjük meg az egyik
előző fejezet legfontosabb megállapításait:
A List tartalmazhat többszörös elemeket, az elemek tárolása legtöbbször tömbszerűn, szekvenciálisan történik, de mivel az elempozíciók indexelve vannak, bármelyik listaelem kiválasztható, módosítható.
Valós életből vett
példák: pizzafutár rendelési címei, GPS-útvonalterv. (Vegyük észre, hogy ez
sok esetben, mint fordított listabejárás visszafelé is működhet!)
Mivel Set és List a Collection, mint közös "szülő" miatt rendkívül közeli rokonságot mutatnak egymással, ezért csak a List néhány további, a Set-től különböző tulajdonságát vizsgáljuk meg. A Set-tel ellentétben a List-ben a következő metódusok jelennek meg:
-
get(int index) - az elem lekérdezése index pozícióban,
-
set(int index, E elem) - az elem cseréje index pozícióban,
-
int indexOf(Object o) - indexszám Object pozícióban,
-
int lastIndexOf(Object o) - a lista utolsó indexszáma,
-
lehetőség van listaösszeillesztéses és részlista műveletek elvégzésére is.
2 db List akkor egyenlő, ha bennük ugyanazon elemek ugyanolyan sorrendben követik egymást.
A listaműveletek vizsgálatakor érdemes kiindulnunk a List egy nagyon hasznos, továbbspecializált típusából, ez a tömblista (ArrayList). Erről a típusról már részletesen írtam egy korábbi fejezetben, jóval a JCF fejezetcsomag születése előtt. Mivel számos értékes információt tartalmaz, a fejezet tartalmát egy az egyben, dőlt betűformátumban ide helyezem át.
Sok programozási esetben hátrány, hogy egy tömb létrehozásakor előre meg kell adnunk a tömb méretét, hiszen meglehet: ezt nem is tudjuk előre. A Java-nyelv viszonylatában már korán felmerült az igény egy olyan típusú tömbstruktúra megalkotására, amely "automatizált" tulajdonságokkal és praktikus, beépített függvényekkel rendelkezik, hogy minél kevesebbet kelljen foglalkoznunk magával a tömb adminisztratív kezelésével. Mivel az egyik leggyakrabban alkalmazott műveletek a listaműveletek, kézenfekvő volt a tömb és a lista előnyös tulajdonságainak egyesítése...
...így jött létre a rendkívül dinamikus tulajdonságokkal rendelkező, éppen ezért nagyon jól kezelhető tömblista (arraylist).
Létrehozása (majdnem) a tömbhöz hasonlóan történik, azzal a kis különbséggel, hogy a tömblista használatához szükséges, előre gyártott rutinok könyvtárát is be kell importálnunk (import java.util.ArrayList;):
import java.util.ArrayList;
public class Main {
public static void main(String args[]) {
ArrayList<String> tomblista = new ArrayList<String>();
}
}
A tömblista, mint objektum voltaképpen az alábbi szabványos deklaráció után születik meg...
ArrayList<String> tomblista = new ArrayList<String>();
...ahol a deklaráció szigorú követelményeinek egyetlen kivétele a tömblista neve (itt tomblista), amely a szokásos névadási konvenciók figyelembevételével bármi lehet.
Java 7v verzióban és felfelé a 2. típusdeklaráció (itt <String>) elmaradhat:
ArrayList<String> tomblista = new ArrayList<>();
Ekkor tehát egy olyan String típusú tárolóobjektum jön létre, amelynek ugyan van egy kezdeti tárolókapacitása (a tömblista valójában szintén tömböt használ fel a tároláshoz), ám az további méretadminisztráció nélkül, dinamikusan változik. (Itt jegyzem meg, hogy a statikus méretű tömbök esetében ezt meg sem tudjuk tenni.) Ezután a tömblista a beépített függvények segítségével menedzselhető. Nézzünk meg néhányat közülük 1-1 jellemző példán keresztül:
add() - tömblista-elem hozzáadása
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya");
System.out.println(tomblista);
}
}
Végeredmény:
[Cirbolya, és , Borbolya]
Észrevehetjük, hogy a tömblista-elemek kiírása is alapjában véve egyszerűvé vált, egyszerű, formázatlan kiíráshoz alapjában véve nem szükséges for ciklust használnunk.
remove(index) - törli a paraméterként megadott indexen lévő tömblista-elemet
Az alábbi futtatható Java-kódban a tömblista 1. indexén lévő " és " stringet töröljük (emlékezzünk vissza, hogy az indexelés mindig 0-val kezdődik!):
import
java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya");
tomblista.remove(1);
System.out.println(tomblista);
}
}
Végeredmény:
[Cirbolya, Borbolya]
clear() - törli az összes tömblista-elemet
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya");
tomblista.clear();
System.out.println(tomblista);
}
}
Végeredmény:
[]
clone() - másolatot készít (klónozza) a tömblistát
import
java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya");
System.out.println(tomblista.clone() == tomblista);
System.out.println(tomblista.clone().equals(tomblista));
}
}
Végeredmény:
false
true
Fontos kihangsúlyozni, hogy a clone() függvény nem duplikálja fizikailag az objektumot, hanem csak az elemek memóriacímeit tárolja el egy új objektumban. Ezt onnan állapíthatjuk meg, hogy tartalmi összehasonlításkor (equals() függvény) true-t kapunk (a 2 objektum tartalmilag valóban azonos), fizikai összehasonlításkor viszont (==) false-t, amely azt mutatja, hogy az utóbbi esetben a memóriacímek lettek összehasonlítva (amelyek nyilvánvalóan különbözőek).
Néhány további függvény:
-
isEmpty() - megnézi, hogy üres-e a tömblista (tartalmaz-e elemet). Ha igen, true-val tér vissza
-
size() - visszaadja a tömblista méretét (elemszámát)
-
get(index) - visszaadja az indexen lévő tömblista-elemet
-
indexOf(objektum) - visszaadja az indexét a tömblista keresett objektuma 1. előfordulásának
import
java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya");
System.out.println(tomblista.isEmpty());
System.out.println(tomblista.size());
System.out.println(tomblista.get(2));
System.out.println(tomblista.indexOf("Cirbolya"));
}
}
Végeredmény:
false
3
Borbolya
0
A tömblista-elemek kiírása tehát alapjában véve egyszerűvé vált, ám komolyabb műveletek elvégzéséhez még mindig iterálnunk kell. A probléma a for ciklus fejléce...
for (i = kezdőérték; i <= végérték; léptetés)
...mivel a tömblista mérete dinamikusan változik, azaz a fejléc adatait csak elég nehézkesen tudjuk beállítani (hiszen megállapítható a size() függvénnyel, lásd a fenti kódban).
A megoldás már a Java alkotóinak is eszébe jutott: ez egy egyszerűsített fejlécű for ciklus, amely csak az objektum nevét és típusát tartalmazza:
for (String element : tomblista)
import
java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya");
for (String element : tomblista) {
System.out.print(element);
}
}
}
Végeredmény:
Cirbolya és Borbolya
Természetesen az iteráció "klasszikus" for ciklussal is megoldható, igaz, kissé döcögősebben: 2 db beépített függvényt is fel kellett használnunk (size() és get()):
import
java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya,");
tomblista.add(" Moha");
tomblista.add(" és ");
tomblista.add("Páfrány");
for (int i = 0; i < tomblista.size(); i++) {
System.out.print(tomblista.get(i));
}
}
}
Végeredmény:
Cirbolya és Borbolya, Moha és Páfrány
Végezetül nézzük meg a fenti Java-kód egyszerűsített fejlécű ciklus-megoldását:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> tomblista = new ArrayList<String>();
tomblista.add("Cirbolya");
tomblista.add(" és ");
tomblista.add("Borbolya,");
tomblista.add(" Moha");
tomblista.add(" és ");
tomblista.add("Páfrány");
for (String element : tomblista) {
System.out.print(element);
}
}
}
Végeredmény:
Cirbolya és Borbolya, Moha és Páfrány
Folytassuk tovább még mindig az ArrayList típussal!
Először nézzünk meg 1 egyszerű, Integer típusú listalétrehozást, amely során megpróbálkozunk duplikált elembevitellel is (4-4):
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista = new ArrayList<Integer>();
lista.add(1);
lista.add(2);
lista.add(3);
lista.add(4);
lista.add(4);
lista.add(5);
System.out.println(lista);
}
}
Végeredmény:
[1, 2, 3, 4, 4, 5]
A duplikált elembevitel sikeres volt, hiszen az azonos elemek külön indexre kerültek, tehát egyedi hozzáférésük biztosított. Ezt a már említett indexOf() és lastIndexOf() metódusokkal tudjuk leellenőrizni:
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista = new ArrayList<Integer>();
lista.add(1);
lista.add(2);
lista.add(3);
lista.add(4);
lista.add(4);
lista.add(5);
System.out.println(lista);
System.out.println("4 első előfordulása a(z) " +
lista.indexOf(4) + ". indexen.");
System.out.println("4 utolsó előfordulása a(z) " +
lista.lastIndexOf(4) + ". indexen.");
}
}
Végeredmény:
[1, 2, 3, 4, 4, 5]
4 első előfordulása a(z) 3. indexen.
4 utolsó előfordulása a(z) 4. indexen.
Az alábbi kódban a szintén új set() és get() metódusok alapszintű működését tanulmányozhatjuk. A listafeltöltés során "rossz", nem növekvő sorrendben adagoltuk az elemeket, amelyet egy egyszerű adatcsereberével (Hogyan cseréljünk meg kezeinkben 2 teli poharat? (adatcserebere) című fejezet), az említett metódusok segítségével javítunk (swap).
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista = new ArrayList<Integer>();
lista.add(1);
lista.add(2);
lista.add(3);
lista.add(5);
lista.add(4);
System.out.println("Lista swap előtt:\n" + lista);
Integer ideiglenes = lista.get(3);
lista.set(3, lista.get(4));
lista.set(4, ideiglenes);
System.out.println("Lista swap után:\n" + lista);
}
}
Végeredmény:
Lista swap előtt:
[1, 2, 3, 5, 4]
Lista swap után:
[1, 2, 3, 4, 5]
A következő, futtatható Java-kódban listák összeillesztése fog történni az addAll() metódus segítségével. A 2 db lista feltöltését 1 lendületben, for ciklus bevetésével menedzseljük:
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista1 = new ArrayList<Integer>();
List<Integer> lista2 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
if(i <= 5) {
lista1.add(i);
continue;
}
lista2.add(i);
}
System.out.println("1. lista kezdetben: " + lista1);
System.out.println("2. lista kezdetben: " + lista2);
lista1.addAll(lista2);
System.out.println("1. lista listaillesztés után: " +
lista1);
}
}
Végeredmény:
1. lista kezdetben: [1, 2, 3, 4, 5]
2. lista kezdetben: [6, 7, 8, 9, 10]
1. lista listaillesztés után: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Illetve listák összeillesztésén felül lehetőségünk van tetszőleges hosszúságú részlisták létrehozására is (subList()):
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista1 = new ArrayList<Integer>();
List<Integer> lista2 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista1.add(i);
}
lista2 = lista1.subList(5, 9);
System.out.println("Főlista: " + lista1);
System.out.println("Allista: " + lista2);
}
}
Végeredmény:
Főlista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Allista: [6, 7, 8, 9]
A következő, futtatható Java-kódban a contains() (tartalmaz-e olyan elemet?) és remove() (elemeltávolítás) metódusok alapszintű használatát tanulmányozhatjuk.
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista1 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista1.add(i);
}
System.out.println("A lista elemei '10' eltávolítása előtt: "
+ lista1);
if(lista1.contains(10)) {
int index = lista1.indexOf(10);
lista1.remove(index);
}
System.out.println("A lista elemei '10' eltávolítása után: "
+ lista1);
}
}
Végeredmény:
A lista elemei '10' eltávolítása előtt: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
A lista elemei '10' eltávolítása után: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Az alábbi 3 db futtatható Java-kódban a listák egyenlőségét ellenőrizzük le, még pedig a már említett szabály alapján:
2 db List akkor egyenlő, ha bennük ugyanazon elemek ugyanolyan sorrendben követik egymást.
Ezt a vizsgálatot háromféleképpen tudjuk megtenni, az elvárt eredményeink a következők lesznek:
ugyanazon elemek ugyanolyan sorrendben követik egymást - true,
ugyanazon elemek más sorrendben követik egymást - false,
az elemek lényegében különböznek (már 1 eltérés is számít, annak ellenére, hogy a többi ugyanazon elem ugyanolyan sorrendben) - false.
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista1 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista1.add(i);
}
System.out.println("1. lista: " + lista1);
List<Integer> lista2 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista2.add(i);
}
System.out.println("2. lista: " + lista2);
System.out.println("Egyenlő a 2 lista? " + "\n" +
lista1.equals(lista2));
}
}
Végeredmény:
1. lista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2. lista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Egyenlő a 2 lista?
true
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista1 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista1.add(i);
}
System.out.println("1. lista: " + lista1);
List<Integer> lista2 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista2.add(i);
}
lista2.set(9, 9);
lista2.set(8, 10);
System.out.println("2. lista: " + lista2);
System.out.println("Egyenlő a 2 lista? " + "\n" +
lista1.equals(lista2));
}
}
Végeredmény:
1. lista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2. lista: [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]
Egyenlő a 2 lista?
false
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista1 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista1.add(i);
}
System.out.println("1. lista: " + lista1);
List<Integer> lista2 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista2.add(i);
}
lista2.set(9, 9);
System.out.println("2. lista: " + lista2);
System.out.println("Egyenlő a 2 lista? " + "\n" +
lista1.equals(lista2));
}
}
Végeredmény:
1. lista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2. lista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 9]
Egyenlő a 2 lista?
false
A JCF további, nagyon érdekes és hasznos metódusokkal szolgálhat nekünk a List felhasználásakor:
-
sort() - rendezi a listát,
-
shuffle() - véletlenszerűen összekeveri a listaelemeket,
-
reverse() - megfordítja a listaelemek sorrendjét,
-
rotate() - adott hosszúságban eltolja a listaelemeket,
-
swap() - felcserél 2 adott pozícióban lévő listaelemet,
-
replaceAll() - az összes előforduló listaelemet kicseréli egy másikra,
-
fill() - felülírja az összes listaelemet egy meghatározott elemre,
-
copy() - átmásolja a forráslistát (source) egy céllistába (destination). Ebben a metódusban figyelnünk kell a forrás-, és céllista méretét, másként hibákba futhatunk.
Nézzünk meg minderre egy egyesített Java-kódot (a metódusok nem feltétlenül a fenti sorrendben fogják követni egymást):
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> lista = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista.add(i);
}
System.out.println("Alaplista: " + lista);
Collections.shuffle(lista);
System.out.println("Lista + shuffle(): " + lista);
Collections.sort(lista);
System.out.println("Lista + sort(): " + lista);
Collections.reverse(lista);
System.out.println("Lista + reverse(): " + lista);
Collections.reverse(lista);
Collections.rotate(lista, 5);
System.out.println("Lista + rotate() 5-tel jobbra: " +
lista);
Collections.sort(lista);
Collections.swap(lista, 0, 9);
System.out.println("Lista + swap() első és utolsó elem
között: " + lista);
Collections.fill(lista, 0);;
System.out.println("Lista + fill() 0 értékkel: " + lista);
Collections.replaceAll(lista, 0, 10);
System.out.println("Lista + replaceAll() 0 érték felülírása
10 értékkel: " + lista);
List<Integer> lista2 = new ArrayList<Integer>();
for(int i = 1; i <= 10; i++) {
lista2.add(i);
}
System.out.println("2. Lista: " +
lista2);
Collections.copy(lista2, lista);
System.out.println("2. Lista + copy() másolás után: " +
lista2);
}
}
Végeredmény:
Alaplista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Lista + shuffle(): [4, 10, 7, 8, 9, 3, 6, 1, 2, 5]
Lista + sort(): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Lista + reverse(): [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Lista + rotate() 5-tel jobbra: [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]
Lista + swap() első és utolsó elem között: [10, 2, 3, 4, 5, 6, 7, 8, 9, 1]
Lista + fill() 0 értékkel: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Lista + replaceAll() 0 érték felülírása 10 értékkel: [10, 10, 10, 10, 10, 10,
10, 10, 10, 10]
2. lista: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2. Lista + copy() másolás után: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]