Objecten vergelijken.

 
28 mei 2008

Een veel voorkomend en veel besproken onderwerp is het vergelijken van objecten. Dit blijft een interessant onderwerp en het kan zeker geen kwaad om er eens in te duiken. De functionaliteit werkt op het eerste gezicht verwarrend en verschilt over talen/frameworks heen. Java en .NET bieden ons een tweetal opties: Equals() en de == operator. In de meeste projecten worden deze door elkaar gebruikt en totaal willekeurig. Is dit handig? Is het niet beter om één te kiezen voor alles? Zijn er ook verschillen tussen beide?

In deze post zal ik voornamelijk ingaan op het verschil tussen de twee methodes in het .Net framework. Hiervoor gebruik ik een op internet veel voorkomende snippet. In deze C# snippet heb ik voor het gemak de uitkomst van de vergelijkingen achter de regel code gezet in commentaar.

Console.WriteLine("Code Snippet #1");

String a = new String(new char[] { 'h', 'e', 'l', 'l', 'o' });
String b = new String(new char[] { 'h', 'e', 'l', 'l', 'o' });

Console.WriteLine(a == b); // 1: true
Console.WriteLine(a.Equals(b)); // 2: true

Object c = a;
Object d = b;

Console.WriteLine(c == d); // 3: false
Console.WriteLine(c.Equals(d)); // 4: true

Wat mij opvalt hier is het resultaat van vergelijking 1. Waarom is dit true terwijl er twee verschillende objecten gemaakt worden? Dat er een verschil is tussen Equals en == blijkt uit het verschil in de resultaten bij 3 en 4. Hierover is genoeg te vinden op MSDN en andere websites. Een korte samenvatting hierover en hoe dit werkt:

Overriding en overloading

De Equals methode is als virtual methode gedefinieerd in System.Object en kan dus overridden worden door elke class. Equals maakt dus gebruik van polymorphisme. De == operator is één van de operatoren die overloaded kan worden. Hierdoor kan men aan de == operator een andere implementatie toekennen binnen een klasse. Mocht de operator niet zijn overloaded, dan pakt de compiler de standaard implementatie voor de == operator.

Manieren van vergelijking

Na gekeken te hebben naar de technische verschillen, kijken we naar de inhoudelijke verschillen van vergelijking. Er zijn twee manieren van vergelijking:

Identity comparison; houdt in dat bij een vergelijking gecheckt wordt of beide referenties naar het zelfde object wijzen.

Value comparison; houdt in dat bij een vergelijking gecheckt zal worden of de achterliggende waarde gelijk is.

De manier van vergelijking wordt niet alleen bepaald door de methode (Equals of ==), maar ook of de objecten die vergeleken worden van het type: value type of reference type zijn. Bij het vergelijken van value types zullen Equals en == standaard value comparison gebruiken. Bij referentie types verschillen Equals en == wel in werking. De implementatie van Equals in System.Object class gebruikt identity comparison. Dus Equals gebruikt standaard identity comparison (en alleen maar value comparison voor classes waarin deze method overridden is). De == operator maakt standaard gebruik van de identity comparison. De enige uitzondering hierop is dat == voor Strings de value comparison gebruikt (dit verklaart ook de uitkomst van vergelijking 1 in de snippet). De keuze hiervoor is wel begrijpelijk aangezien Strings immutable zijn. Immers, de enige String-input voor de identity comparison die true oplevert is namelijk de String vergeleken met zichzelf. In Java is hier overigens trouwens niet voor gekozen en levert vergelijking 1 false als resultaat.

Maar welke methode moet men nou gebruiken? Dit is dus afhankelijk van waarop men wil vergelijken. Als men referentie types wil vergelijken op waarde dan is het logisch Equals te gebruiken, wil men vergelijken op referenties dan kan men het beste == gebruiken. In .Net moet er dan wel op gelet worden dat de == operator zich voor Strings iets anders gedraagt.

Hoe zou dit in andere talen zijn..?


Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Development, .Net

Tags: ,


Reacties (10)

  • Taan schreef:

    Ik wil 2 identieke chars uit mijn string filteren zoals bv radAar -> wordt radar om de A = a dus 1 a maar. Maar hoe ik dit??

    Geplaatst op 17 december 2008 om 11:44 Permalink

  • Nanne schreef:

    Om het zelfs nog leuker te maken in Java:

    String a = new String(“aap”);
    String b = new String(“aap”);

    a == b –> false (zie boven)
    a.intern() == b.intern() –> true

    Geplaatst op 07 juni 2008 om 20:06 Permalink

  • Thomas Zeeman schreef:

    In Java ben je afhankelijk van de String constant pool en het gedrag dat nodig is om strings daar in te krijgen.
    Kort gezegd, als je met String literals werkt (alles wat tussen “s staat), dan werkt de == operator gelijk aan de equals methode, maar als je in Java met de new operator strings aanmaakt, dan werkt de == anders dan de equals methode.
    Dat verklaart ook gelijk de uitkomst van de code die Ivo in comment 5 geeft. Die gebruikt string literals, dit in tegenstelling tot de code van Karin waar de new operator gebruikt wordt.

    Mocht je meer weten over dit (of ander obscuur gedrag in Java), dan kan ik de Java Language Spec van harte aanbevelen. Met name de String kent nog wel wat meer van dit soort ‘optimalisaties’.

    Geplaatst op 28 mei 2008 om 15:08 Permalink

  • Tammo schreef:

    Huh?
    Is de tijd omgedraaid?
    Reacties 1, 2, 3 geven antwoord op reacties 4 en 5.
    Maar het punt is dus een beetje dat als je in java == gaat gebruiken om strings te vergelijken er van alles uit kan komen afhankelijk van de compiler (en van de stand van de maan). Wat je wilt gebruiken bij strings is gewoon equals.

    Geplaatst op 28 mei 2008 om 12:56 Permalink

  • Chris de Kok schreef:

    @Ivo: Je moet inderdaad in C# als je Equals() override ook GetHashCode() overriden. Dit omdat de default implementatie van GetHashCode() niet garandeerd dat het object uniek is.

    Geplaatst op 28 mei 2008 om 12:25 Permalink

  • Ivo Limmen schreef:

    Vreemd. Ik ben ook even gaan stoeien met de code. Als je het volgende maakt in Java:

    String a = “hello”;
    String b = “hello”;
    System.out.println(a == b);
    System.out.println(a.equals(b));

    Zou je hetzelfde resultaat verwachten als dat van Karin, dit is echter niet het geval. Dit bovenstaande geeft:
    true
    true

    Geplaatst op 28 mei 2008 om 12:01 Permalink

  • In Java werkt het anders dan in C#.
    Als je de volgende code uitvoert in Java:

    String a = new String(new char[] { ‘h’, ‘e’, ‘l’, ‘l’, ‘o’ });
    String b = new String(new char[] { ‘h’, ‘e’, ‘l’, ‘l’, ‘o’ });
    System.out.println(a == b);
    System.out.println(a.equals(b));

    Object c = a;
    Object d = b;
    System.out.println(c == d);
    System.out.println(c.equals(d));

    Krijg je het resultaat:
    false
    true
    false
    true

    Dit komt omdat de == operator in Java alleen kijkt of de referenties hetzelfde zijn, terwijl de equals operator de objecten zelf vergelijkt. Voor eigen objecten moet je de equals overigens zelf eerst nog implementeren.

    Geplaatst op 28 mei 2008 om 10:21 Permalink

  • Ivo Limmen schreef:

    Ook in Java wordt er gebruikt van een String resource pool. Het voorbeeld van Sjors Miltenburg zal in Java (met kleine aanpassingen) dan ook hetzelfde resultaat geven.
    Wat ik overigens mis in je verhaal is de Hashcode methode. Als je de implementatie van equals opnieuw implementeert in Java zal je ook hashcode opnieuw moeten implementeren. Is dit in C# ook niet het geval?

    Geplaatst op 28 mei 2008 om 10:08 Permalink

  • Chris de Kok schreef:

    Dit klopt inderdaad. .NET gebruikt voor het optimaliseren van string initialisatie de .NET String internal pool. Check voor een korte uitleg: http://msmvps.com/blogs/manoj/archive/2004/01/09/1549.aspx en voor een iets uitgebreidere uitleg: http://www.devx.com/vb2themax/Tip/18698

    Geplaatst op 28 mei 2008 om 8:56 Permalink

  • Sjors Miltenburg schreef:

    Toevallig hadden Erik, Stefan en ik het hier gisteren over. Er blijkt nog iets te zijn waar je rekening mee moet houden:

    string s1 = “hello”;
    string s2 = “hello”;
    string s3 = “h”;
    s3 = s3 + “ello”;

    Console.WriteLine(s1 == s2);
    Console.WriteLine((object)s1 == (object)s3);
    Console.WriteLine((object)s1 == (object)s2);
    Console.ReadLine();

    Result:
    True
    False
    True

    De reden dat het laatste result True teruggeeft blijkt te zitten in een compiler optimalisatie.
    Doordat een string een immutable en een reference type is en de compiler ziet dat ze dezelfde waarde hebben wordt er slechts een object voor aangemaakt in het geheugen.

    Geplaatst op 28 mei 2008 om 8:40 Permalink