Gyakorlati alapok II.

Mikor egyenlő az egyenlő avagy az equals() függvény

 

A mindennapi életben gyakran mérlegelünk, azaz hasonlítunk össze dolgokat egymással.

www.informatika-programozas.hu

 

Ezen összehasonlítások legtöbbje bizony felületes, a dolgok mértéke legtöbbször a hétköznapi praktikum szintjén mozog és valójában az esetek döntő részében ez elég is. Adott például a reggeli kávé elfogyasztásához használt néhány darab kockacukor.

 

www.informatika-programozas.hu

 

Igazából nem érdekel minket, ha a cukorkockák mikrosúlyban kissé különböznek egymástól, sőt: teljes joggal őrültnek volnánk nézve, ha ilyen irányú felháborodásunkat másokkal is megosztanánk.

 

Míg tehát a mindennapi élet egyszerű és felületes összehasonlítási elveken nyugszik, a számítógépnek ez a szintű, lanyha, emberi léptékű méricskélés sajnos nem elégséges, neki pontos, egzakt bemeneti adatok és jól megépített program szükséges korrekt kimeneti adatok szolgáltatásához, illetve még valami: belső értelem és logika. Hiszen össze tudunk-e hasonlítani egy betűt és egy számot egymással? Az az igazság, hogy bizonyos esetekben igen, például ha sikerül olyan közös tulajdonságokat találni bennük, amelyek éppen akkor számunkra fontosak, ilyen lehet például színük, méretük, betűkészletük, helyzetük, stb. Más esetben viszont mindez nem lényeges és csakis azon információkra vagyunk kíváncsiak, amelyeket ezen szimbólumok hordoznak. Adott például a 2, mint szám és a B, mint betű. Nem lesz értelmezhető sem ember, sem gép számára az alábbi művelet és szó:

 

2 + A = ?

 

B2BA

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Az összehasonlításoknak, vele az egyenlőség megmérésének tehát mindig belső értelmet kell hordozniuk, ezt nem hagyhatjuk figyelmen kívül, másként rossz eredményeket kapunk vagy rosszul fogunk dönteni. Ezt a belső értelmet a számítógép egyébként sem érzékeli, legfeljebb a programozók alkotta fejlesztői programkörnyezetek fognak minket figyelmeztetni a belső logikátlanságokra.

 

Az equals() függvény használatakor keresnünk és követnünk kell ezt a belső értelmet és még nem is beszéltünk a további, Java-specifikus jellemzőkről. A szabályok tehát bonyolultak és garantáltan nem 100%-osak.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Hiszen minél bonyolultabb egy programozási nyelvi környezet, annál nagyobb az esélye, hogy működése során szabályszegő kivételekkel találkozhatunk;  (igazából minden okunk megvan annak kijelentésére, hogy a Java az egyik lehető legbonyolultabb programozási nyelv), de érdemes az alapelveket alapszinten, felsorolásszerűen lefektetni.

 

Figyeljünk a belső logikára

A fenti betűs-számos példa megmutatta, hogy találhatunk közös tulajdonságokat egymással alapvetően össze nem hasonlítható dolgok esetében is, ám az bizonyos, hogy ha ilyen jellegű feladattal szembesülünk, akkor azt egészen máshogy, jóval bonyolultabban kell megoldanunk, ehhez  egészen más függvényeket kell elővennünk.

 

Figyeljünk a típusegyezésre

A Java erősen típusos nyelv és bár a programozási környezet szoftver-automatikája sokféle problémát képes detektálni, ám egy adott tartalmi szint fölé már nem tud hatolni.

 

Az alábbi egyszerű, futtatható Java-kód például összehasonlít egy char és int típusú változót, amelynek értékei mindkettőben voltaképpen 1, sőt: a kód átmegy a fordító összes ellenőrzésén és lefut. A belső logika döcögve ott van, ám hiányzik belőle a Java-specifikus szemlélet, mert 2 különböző (egyszerű) adattípust ilyen direktben nem érdemes összehasonlítanunk. A végeredmény csakis false lehet:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    char karakter = '1';
    int szam = 1;
    System.out.println(karakter == szam);
    }
}
 

Végeredmény:

false

 

A helyzet tovább rosszabbodik, ha karakter helyett már összetett String adattípust próbálunk a fordítóra rásózni. Ezt a kontárkodást már nem bírja elviselni és tiltakozásképpen nyíltan hibaüzenetet küld:

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    int szam = 1;
    System.out.println(szam == s);
    }
}

 

Végeredmény:

Hibaüzenet - Imcompatible operand types int and String

 

A megoldás csakis az lehet, ha az egyik típust átkonvertáljuk a másikba. Ezt először a Integer.parseInt() függvény segítségével oldjuk meg. Az irány String - int:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    int szam1 = 1;
    int szam2 = Integer.parseInt(s);
    System.out.println(szam1 == szam2);
    }
}

 

Végeredmény:

true

 

De működik az Integer.valueOf() függvénnyel is:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    int szam1 = 1;
    int szam2 = Integer.valueOf(s);
    System.out.println(szam1 == szam2);
    }
}

 

Végeredmény:

true

 

Nézzük meg a konvertálást a másik irányba is, az irány int - String a toString() metódus segítségével:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    int szam = 1;
    String s2 = Integer.toString(szam);
    System.out.println(s == s2);
    }
}

 

Végeredmény:

false

 

A kód lefut, a végeredmény mégis false!

 

A probléma már részletesen ismertetésre került a String című fejezetben. Ebben az esetben a Java pusztán lokáció, azaz memóriahely-foglalás, valójában memóriacím (referencia) szerint hasonlította össze a String példányokat. A végeredmény ebből a szempontból csakis false lehet, mert a 2 objektum memóriacíme különböző.

 

Pontosan a tartalmi összehasonlításhoz kell előrántanunk az övtáskából az equals() függvényt:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    int szam = 1;
    String s2 = Integer.toString(szam);
    System.out.println(s.equals(s2));
    }
}

 

Végeredmény:

true

 

Láthatjuk tehát, hogy az equals() függvény mennyire sokoldalú, mégis az összehasonlítás legelső lépését mégis mindig magunknak kell megtenni azzal, hogy összehasonlításkor csakis olyan elemeket engedünk közel egymáshoz, amelyek a belső logika szerint egyáltalán összehasonlíthatók.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Alapértelmezésben egyszerű adattípus esetén nem szükséges vagy nem lehetséges az equals() függvény használata, ilyen esetekben alkalmazzunk == operátort. Az equals() függvényt az összetett adattípusok esetén használjuk.

 

Az equals() függvénynek további tartalmi követelményeknek is meg kell felelnie:

Reflexió

Az x.equals(x) egyenlőségnek mindig igaznak kell lennie.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    System.out.println(s.equals(s));
    }
}

 

Végeredmény:

true

 

Szimmetrikusság

Ha y.equals(x) igaz, akkor x.equals(y) is igaz.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    String s2 = "1";
    System.out.println(s.equals(s2));
    System.out.println(s2.equals(s));
    }
}

 

Végeredmény:

true

true

 

Tranzitivitás

Ha x.equals(y) igaz és y.equals(z) igaz, akkor x.equals(z) is igaz.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    String s2 = "1";
    String s3 = "1";
    System.out.println(s.equals(s2));
    System.out.println(s2.equals(s3));
    System.out.println(s.equals(s3));
    }
}

 

Végeredmény:

true

true

true

 

Konzisztencia

Akárhányszor hívjuk meg az x.equals(y) függvényt, mindig ugyanazt az értéket adja vissza. Ezt most egy 20 fordulós for ciklussal próbáljuk modellezni és csak reménykedünk abban, hogy ugyanaz lesz a végeredmény 921.345.111. ciklusban is.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    String s2 = "1";
    for(int i = 0; i < 20; i++){
        System.out.println(s.equals(s2));
        }
    }
}

 

Végeredmény:

true
true
true
true
true
true
true
true
true
true
true
true
true
true
true
true
true
true
true
true

 

Elszántabbak bevethetnek végtelen ciklust is, hátha fülön csípnek néhány évtized múlva egy false-ot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    String s2 = "1";
    for(; ;){
        System.out.println(s.equals(s2));
        }
    }
}

 

Végeredmény:

true
true
true
true
true
true
true
végtelen ciklusban...


0-érték hamisság

Az x.equals(null) egyenlőség mindig hamis.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
public static void main(String[] args) {
    String s = "1";
    String s2 = null;
    System.out.println(s.equals(s2));
    }
}

 

Végeredmény:

false

 

Bár a String adattípus is már "elég összetett" ahhoz, hogy rajta plasztikusan tanulmányozhassuk az equals() függvényt, most nézzünk meg ilyen jellegű eljárásokat egy másik összetett adattípuson, mondjuk 2 db statikus méretű, int típusú tömbön. Először kövessük el immár klasszikussá vált hibánkat: hasonlítsuk össze a 2 tömböt az == operátorral. Emlékezzünk vissza, miért hiba ez: az objektumok ekkor nem tartalom, hanem memóriahely (referencia) szerint lesznek összehasonlítva, ezért a végeredmény false lesz:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    int [] tomb1 = new int[] {1, 2, 3, 4, 5};
    int [] tomb2 = new int[] {1, 2, 3, 4, 5};
    System.out.println(tomb1 == tomb2);
    }
}

 

Végeredmény:

false

 

Az equals() függvény katonai bevetésével minden a helyére kerülne...

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    int [] tomb1 = new int[] {1, 2, 3, 4, 5};
    int [] tomb2 = new int[] {1, 2, 3, 4, 5};
    System.out.println(tomb1.equals(tomb2));
    }
}

 

Végeredmény:

false

 

...ha true végeredményt kaptunk volna, de nem!

 

Mi a hiba?

Most hiba kivételesen nincs; az equals() függvény hivatalos dokumentációjában találhatjuk meg a probléma okát:

 

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).

Magyarázat: tömb esetében az equals() függvény nem a tömbök elemeit hasonlítja össze, hanem a tömbök referenciáit és csakis akkor lesz true a végeredmény, ha a referenciák azonosak. (Ez voltaképpen ugyanazt jelenti, mintha == operátorral hasonlítottuk volna össze őket.) Tehát a true végeredmény equals() függvénnyel csakis akkor lehetséges, ha a tömb önmagával van összehasonlítva:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    int [] tomb1 = new int[] {1, 2, 3, 4, 5};
    System.out.println(tomb1.equals(tomb1));
    }
}

 

Végeredmény:

true

 

A megoldás egy másik függvény bevetése (Arrays.equals()), amely a tömbök elemeit hasonlítja össze. Ekkor a hivatalos dokumentáció szerint...

 

...two arrays are equal if they contain the same elements in the same order...

 

...2 tömb akkor egyenlő, ha ugyanazon elemeket tartalmazzák ugyanabban a sorrendben. Nézzük meg a futtatható implementációt, amely mindkét függvényt magában foglalja:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.util.Arrays;

public class Main {
public static void main(String[] args) {
    int [] tomb1 = new int[] {1, 2, 3, 4, 5};
    int [] tomb2 = new int[] {1, 2, 3, 4, 5};
    System.out.println(tomb1.equals(tomb2));
    System.out.println(Arrays.equals(tomb1, tomb2));
    }
}

 

Végeredmény:

false

true

 

Látható tehát, hogy egy ilyen általános célú függvény implementálásakor mennyi gondja akad a Java alkotóinak! Le a virtuális kalappal előttük!

 

www.informatika-programozas.hu