[java] nullpointer probleem

Pagina: 1
Acties:

  • Kevinp
  • Registratie: Juni 2001
  • Laatst online: 08-05 12:43
het gaat om het volgende stukje code

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public int totaleScore()
     {   
        int totaal = 0;
        for (int i = 0 ; i < 3 ; i++) 
                {
                    if (!darts[i].equals(null))
                {
                     totaal += dartScore(darts[i]);
                   
                }
        
        }
        return totaal;
           
    }


de array darts is een String[3], daarin staat "T20" bv 3x, als dat het geval is werkt de code. Als er maar 1 of 2 keer T20 staat dan krijg je en nullpointer. Dit wilde ik afvangen met de code "!darts[i].equals(null)" Maar dit werkt niet. ook null vervangen door "" werkt niet. weet iemand hoe ik dit wel kan afvangen?

Ooh ja, laat ik helemaal eerlijk zijn. Dit stukje code komt uit een hertentamen (over debuggen) van java, ik heb hem niet hoeven maken, maar was wel benieuwd hoe moeilijk die was. En dit is het enige waar ik (totaal) niet uit kom. Het is dus geen huiswerk het is meer nieuwsgierigheid over de oplossing.

[ Voor 20% gewijzigd door Kevinp op 31-03-2005 16:02 ]

d'r is maar één ding in het leven wat moet, en dat is dood gaan.


  • whoami
  • Registratie: December 2000
  • Laatst online: 01:02
code:
1
if( darts[i] != null )


werkt dat niet ?

Als je het doet mbhv die equals method, dan ga je nl. de equals method gaan oproepen van het element dat dan in die array zit. Echter, op die positie zit er niets in de array, er zit null in, dus ga je die equals gaan oproepen van een element dat niet bestaat, dan krijg je een nullpointerexception.

[ Voor 76% gewijzigd door whoami op 31-03-2005 16:03 ]

https://fgheysels.github.io/


  • Kevinp
  • Registratie: Juni 2001
  • Laatst online: 08-05 12:43
whoami schreef op donderdag 31 maart 2005 @ 16:02:
code:
1
if( darts[i] != null )


werkt dat niet ?

Als je het doet mbhv die equals method, dan ga je nl. de equals method gaan oproepen van het element dat dan in die array zit. Echter, op die positie zit er niets in de array, er zit null in, dus ga je die equals gaan oproepen van een element dat niet bestaat, dan krijg je een nullpointerexception.
ja dat werkt, het kan soms zo simpel zijn terwijl je lang moeilijk bezig bent. Verder is je uitleg ook nog duidelijk ook.

d'r is maar één ding in het leven wat moet, en dat is dood gaan.


Verwijderd

string == string geeft false op ook al zit er hetzelfde woord in. De pointers naar het geheugenblokje waar het object string daadwerkelijk woont zijn immers verschillend. De ene string woont achter adres 12 en de tweede 15, ook al zijn ze verder hetzelfde.
Daarvoor is de equals, die kijkt niet naar wat die pointer is maar die vergelijkt de objecten die op de plaats zitten waar de pointer heen wijst.

Wil je weten of de pointer helemaal nergens heen wijst, dus null is, dan is equals dus niet de juiste methode. Je moet dan gewoon == of != gebruiken.

[ Voor 14% gewijzigd door Verwijderd op 31-03-2005 16:12 ]


  • Jrz
  • Registratie: Mei 2000
  • Laatst online: 00:54

Jrz

––––––––––––

XX.equals(null) moet altijd false terug geven...

Van de docs:
For any non-null reference value x, x.equals(null) should return false

whoami heeft de oplossing al gegeven

Volgens mij moet je nog een hertentamen doen ;)

Ennnnnnnnnn laat losssssssss.... https://github.com/jrz/container-shell (instant container met chroot op current directory)


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 02-05 01:32
Verwijderd schreef op donderdag 31 maart 2005 @ 16:11:
string == string geeft false op ook al zit er hetzelfde woord in. De pointers naar het geheugenblokje waar het object string daadwerkelijk woont zijn immers verschillend. De ene string woont achter adres 12 en de tweede 15, ook al zijn ze verder hetzelfde.
Daarvoor is de equals, die kijkt niet naar wat die pointer is maar die vergelijkt de objecten die op de plaats zitten waar de pointer heen wijst.
Het verradelijke is dat dat niet per se zo hoeft te zijn. Deze code print bijvoorbeeld gewoon True, wat je niet zou verwachten:
Java:
1
2
3
4
5
6
7
8
9
public class Test
{
        static String foo() {
                return "f" + "o" + "o";
        }
        public static void main(String[] args) {
                System.out.println(foo() == "foo");
        }
}

Als de strings die je gebruikt dus op zo'n manier geconstrueerd worden (ze zijn allemaal hardcoded en je gebruikt ze feitelijk als een soort enumeratie-waarden) dan gaat de vergelijking gewoon goed! Pas als je ze expliciet met new hebt gealloceerd garandeert de JVM je dat het afzonderlijke objecten zijn.

Eigenlijk moet je strings inhoudelijk altijd met equals vergelijken, maar dit gedrag is extra verraderlijk omdat het in het begin goed lijkt te werken. Pas als je later op een iets andere manier strings construeert (niet eens noodzakelijkerwijs met new) kan het fouten gaan. En dan kun je lekker gaan zitten debuggen.

Moraal van het verhaal: als objecten inhoudelijk wil vergelijken moet je altijd equals gebruiken.

[ Voor 8% gewijzigd door Soultaker op 31-03-2005 18:06 ]


  • Kevinp
  • Registratie: Juni 2001
  • Laatst online: 08-05 12:43
Jrz schreef op donderdag 31 maart 2005 @ 16:21:
XX.equals(null) moet altijd false terug geven...

Van de docs:
For any non-null reference value x, x.equals(null) should return false

whoami heeft de oplossing al gegeven

Volgens mij moet je nog een hertentamen doen ;)
Misschien wel, maar het tentamen voor het tentamen wat ik had had ik een 10 dus zo slecht is het niet met mij gesteld. Alleen zie je dit kleine dingentje over het hoofd. En dat van dat == had ik al geprobeerd. Alleen die oplossing was een combinatie waardoor die niet werkte wat was nl iets van !dart[i] == null (ja dat is ook erg slecht, maar ik kwam er neit meer uit).

Moraal van het verhaal: als objecten inhoudelijk wil vergelijken moet je altijd equals gebruiken. dat wilde ik ook doen, maar in dit geval werkte dat niet.

[ Voor 11% gewijzigd door Kevinp op 31-03-2005 16:48 ]

d'r is maar één ding in het leven wat moet, en dat is dood gaan.


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 02-05 01:32
Hmm nee, in jouw code wil je juist niet inhoudelijk vergelijken. Je wil weten of de referentie wel (non-null) of niet (null) naar een object verwijst. Dan moet je dus juist geen equals gebruiken, want wat de inhoud/waarde van het object is, is op dat punt niet interessant.

Equals met een null-argument is daarom juist verkeerd, omdat je met equals vraagt om twee objecten inhoudelijk te vergelijken, terwijl null juist het afwezig zijn van inhoud symboliseert. Klinkt heel zweverig misschien, maar als je er goed over nadenkt is het juist een heel concreet punt. De keerzijde van mijn eerdere stelling is dan ook: vergelijken met null doe je altijd met ==.

Eigenlijk zou equals conceptueel gezien een NullPointerException mogen gooien als je 'm een null argument meegeeft; dat lost ook het probleem op dat je wel obj.equals(null) kunt schrijven, maar niet null.equals(obj). (Sowieso is het nogal vaag dat in Java a.equals(b) niet per se gelijk is aan b.equals(a), wat me ook reden lijkt om te concluderen dat equality testing in Java eigenlijk gewoon stuk is.)

[ Voor 6% gewijzigd door Soultaker op 31-03-2005 17:30 ]


Verwijderd

Strings zijn sowieso een beetje vaag in Java. En dat zou het zogenaamd makkelijker moeten maken.

Verder een goede aanvulling Soultaker

[ Voor 18% gewijzigd door Verwijderd op 31-03-2005 18:01 ]


Verwijderd

Soultaker schreef op donderdag 31 maart 2005 @ 17:30:
Sowieso is het nogal vaag dat in Java a.equals(b) niet per se gelijk is aan b.equals(a), wat me ook reden lijkt om te concluderen dat equality testing in Java eigenlijk gewoon stuk is.
Wanneer is dat dan het geval?
Verwijderd schreef op donderdag 31 maart 2005 @ 18:00:
Strings zijn sowieso een beetje vaag in Java. En dat zou het zogenaamd makkelijker moeten maken.
Hoezo vaag? Het is doodsimpel: als jij de compiler strings gaat laten (door aanhalingstekens te gebruiken) aanmaken zullen attributen naar dezelfde instantie wijzen. Dit kan domweg omdat strings in java non-mutable zijn.

[ Voor 40% gewijzigd door Verwijderd op 31-03-2005 18:15 ]


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 02-05 01:32
Dat is meestal zo als je twee objecten van verschillende klassen vergelijkt. De min-of-meer standaardcode voor een implementatie equals voor een klasse K is:
Java:
1
2
3
4
5
6
7
public boolean equals(Object other)
{
    if(other == null)       return false;
    if(!other instanceof K) return false;
    K that = (K)other;
    return .... ;
}

Op de puntjes staat dan een of andere expressie die alle relevante velden vergelijkt ofzoiets.

Het gaat nu mis wanneer a en b objecten zijn van klassen A en B respectievelijk, en B is afgeleid van A. a.equals(b) kan nu best true opleveren, omdat alle velden die in A gedefinieerd zijn overeenkomen, maar b.equals(a) levert altijd false op omdat a geen instantie is van klasse B (maar b wel van klasse A).

Als je dus graag die symmetrie (die ook in de Java API staat beschreven: Object.equals) wil hebben, moet je dus eisen dat A en B van exact dezelfde (en niet slechts een equivalente) klasse zijn. Maar dan is het in Java direct onmogelijk geworden om twee objecten van verschillende klassen te vergelijken. Die symmetrie wordt dus in de praktijk niet geboden (en transitiviteit ook niet, denk ik).
Hoezo vaag? Het is doodsimpel: als jij de compiler strings gaat laten (door aanhalingstekens te gebruiken) aanmaken zullen attributen naar dezelfde instantie wijzen. Dit kan domweg omdat strings in java non-mutable zijn.
Het is vaag omdat het niet precies duidelijk is hoe 'slim' de compiler is. In mijn voorbeeld vergeleek ik de string "foo" met het resultaat van een functie, waar in de functie een expressie staat die de strings "f", "o" en "o" concateneert. Dat ik als ik twee keer "foo" schrijf een referentie naar dezelfde instantie krijg is nog te bevatten. Dat het nog steeds zo is als ik een expressie schrijf die evalueert naar een waarde waar een andere expressie toevallig ook naar evalueert is raar, want at runtime is dat zeker niet zo.

Als ik de code zo aanpas, wordt er weer wel false geprint:
Java:
1
2
3
public static void main(String[] args) { 
    System.out.println("x"+foo() == "xfoo"); 
}

Waarom? Ook hier zou de compiler in theorie best kunnen bedenken dat "x"+foo() naar "xfoo" evalueert (want foo() is een static method). Maar dat doet 'ie niet! Het onderscheid wanneer dit wel gebeurt en wanneer niet is voor mijn in ieder geval onduidelijk en het lijkt me dan ook willekeur. Vandaar dat het verradelijk is om op dit gedrag te vertrouwen. Eigenlijk zou je dus alleen referenties van objecten moeten vergelijken die je echt met new hebt met geïnstantieerd!

[ Voor 39% gewijzigd door Soultaker op 31-03-2005 18:29 ]


  • Macros
  • Registratie: Februari 2000
  • Laatst online: 30-04 09:28

Macros

I'm watching...

Je moet de equals niet met instanceof implementeren maar met .getClass() en className.class, dan heb je dat probleem helemaal niet.

[ Voor 10% gewijzigd door Macros op 31-03-2005 20:05 ]

"Beauty is the ultimate defence against complexity." David Gelernter


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 02-05 01:32
Mja, dat zei ik dus ook in mijn post en het gevolg: "... dan is het in Java direct onmogelijk geworden om twee objecten van verschillende klassen te vergelijken." Dat lijkt me niet direct wenselijk en in de praktijk werkt het ook niet zo; in de praktijk wordt veel instanceof gebruikt juist omdat die strikte symmetrie helemaal niet altijd zo handig is.

[ Voor 3% gewijzigd door Soultaker op 01-04-2005 02:06 ]


  • Macros
  • Registratie: Februari 2000
  • Laatst online: 30-04 09:28

Macros

I'm watching...

Soultaker schreef op vrijdag 01 april 2005 @ 02:06:
Maar dan is het in Java direct onmogelijk geworden om twee objecten van verschillende klassen te vergelijken.
[...]
Mja, dat zei ik dus ook in mijn post en het gevolg: "... dan is het in Java direct onmogelijk geworden om twee objecten van verschillende klassen te vergelijken." Dat lijkt me niet direct wenselijk en in de praktijk werkt het ook niet zo; in de praktijk wordt veel instanceof gebruikt juist omdat die strikte symmetrie helemaal niet altijd zo handig is.
Als twee classen van een verschillende klassen zijn, dan zijn ze dus per definitie nooit gelijk... Dan heb je je strikte symmetrie. Gebruik je instanceof dan introduceer je zelf die insymmetrie.
Wat bedoel je anders met "onmogelijk"? Je kan ze wel vergelijken, maar het is dan gewoon altijd false. Als ze van een verschillend type zijn, dan kan dan 1 andere attributen hebben dan de ander, die kan je of niet vergelijken, of je krijgt een class cast exception als je het ene object de andere laat bekijken. Ik zie jouw probleem helemaal niet. Geef anders eens een echt voorbeeld waar het fout gaat.

[ Voor 30% gewijzigd door Macros op 01-04-2005 08:27 ]

"Beauty is the ultimate defence against complexity." David Gelernter


  • Salandur
  • Registratie: Mei 2003
  • Laatst online: 10:41

Salandur

Software Engineer

Soultaker schreef op donderdag 31 maart 2005 @ 18:23:
[...]

Het is vaag omdat het niet precies duidelijk is hoe 'slim' de compiler is. In mijn voorbeeld vergeleek ik de string "foo" met het resultaat van een functie, waar in de functie een expressie staat die de strings "f", "o" en "o" concateneert. Dat ik als ik twee keer "foo" schrijf een referentie naar dezelfde instantie krijg is nog te bevatten. Dat het nog steeds zo is als ik een expressie schrijf die evalueert naar een waarde waar een andere expressie toevallig ook naar evalueert is raar, want at runtime is dat zeker niet zo.

Als ik de code zo aanpas, wordt er weer wel false geprint:
Java:
1
2
3
public static void main(String[] args) { 
    System.out.println("x"+foo() == "xfoo"); 
}

Waarom? Ook hier zou de compiler in theorie best kunnen bedenken dat "x"+foo() naar "xfoo" evalueert (want foo() is een static method). Maar dat doet 'ie niet! Het onderscheid wanneer dit wel gebeurt en wanneer niet is voor mijn in ieder geval onduidelijk en het lijkt me dan ook willekeur. Vandaar dat het verradelijk is om op dit gedrag te vertrouwen. Eigenlijk zou je dus alleen referenties van objecten moeten vergelijken die je echt met new hebt met geïnstantieerd!
Het schijnt dat de JVM Strings-objecten niet meerdere keren in het geheugen zet, ook al definieer je ze allebei met new. Er wordt dus ook ergens anders nog bijgehouden of de inhoud van een string al in het geheugen staat en bij een wijziging wordt er dan weer een nieuwe string in het geheugen geplaatst.

Java:
1
2
3
4
5
6
String s1 = "Amsterdam"; 
String s2 = "Amsterdam"; // Beide strings wijzen naar hetzelfde geheugen adres
                          // s1 == s2 kan true zijn, maar hoeft niet!

s2 += " is mooi"; // Er wordt hier naar een ander geheugen adres gewezen
                  // s1 == s2 is hier zeker weten false

De JVM is dus qua geheugengebruik flink geoptimaliseerd, maar dat levert niet altijd wenselijk gedrag op.

In mijn geval (jvm 1.4.2_07) levert dit eerst true, daarna false.

[ Voor 21% gewijzigd door Salandur op 01-04-2005 08:49 ]

Assumptions are the mother of all fuck ups | iRacing Profiel


  • Macros
  • Registratie: Februari 2000
  • Laatst online: 30-04 09:28

Macros

I'm watching...

'==' Vergelijkt primitive values, dus de geheugen adressen als het objecten zijn. Wil je Strings comparen moet je gewoon altijd equals gebruiken.
Als je dan toch perse '==' gebruikt, dan moet je zelf weten wanneer het fout gaat. Het is gewoon simpel.
Als 2 objecten '==' gelijk zijn, dat weet je dat het hetzelfde object is. Als ze dat niet zijn weet je nog verder niks over de inhoud.

"Beauty is the ultimate defence against complexity." David Gelernter


  • Salandur
  • Registratie: Mei 2003
  • Laatst online: 10:41

Salandur

Software Engineer

Macros schreef op vrijdag 01 april 2005 @ 11:42:
'==' Vergelijkt primitive values, dus de geheugen adressen als het objecten zijn. Wil je Strings comparen moet je gewoon altijd equals gebruiken.
Als je dan toch perse '==' gebruikt, dan moet je zelf weten wanneer het fout gaat. Het is gewoon simpel.
Als 2 objecten '==' gelijk zijn, dat weet je dat het hetzelfde object is. Als ze dat niet zijn weet je nog verder niks over de inhoud.
Uit mijn voorbeeld blijkt dat dat niet altijd het geval is, want s1 en s2 zijn 2 verschillende objecten en allebei apart gedeclareerd. Toch worden ze bij '==' als gelijk aangeduid.

Assumptions are the mother of all fuck ups | iRacing Profiel


Verwijderd

Soultaker schreef op donderdag 31 maart 2005 @ 18:23:

Java:
1
2
3
4
5
6
7
8
public boolean equals(Object other)
{
    if(other == null)       return false;
    if(other == this)       return true; //why perform all the field checks if it is the same pointer.
    if(!other instanceof K) return false;
    K that = (K)other;
    return .... ;//2 different objects, do field check
}
heel kleine wijziging...zoals je ze in bijvoorbeeld String.equals terugvindt.

  • Macros
  • Registratie: Februari 2000
  • Laatst online: 30-04 09:28

Macros

I'm watching...

Salandur schreef op woensdag 06 april 2005 @ 09:39:
[...]


Uit mijn voorbeeld blijkt dat dat niet altijd het geval is, want s1 en s2 zijn 2 verschillende objecten en allebei apart gedeclareerd. Toch worden ze bij '==' als gelijk aangeduid.
Nee, dat klopt niet. Bij Strings hoeft het niet zo te zijn dat het twee aparte objecten zijn als ze apart gedeclareerd zijn.

"Beauty is the ultimate defence against complexity." David Gelernter


  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

Overigens is dit de code van de String.equals:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }

Lekker tricky is dat de null-check in de instanceof zit verborgen. En inderdaad, als je deze klasse zou kunnen overriden zou ie met een subklasse ook nog true geven als dat object dezelfde string-inhoud heeft.
Maar het is aardig filosofisch om te beslissen of dat wel of niet correct is :)

  • Macros
  • Registratie: Februari 2000
  • Laatst online: 30-04 09:28

Macros

I'm watching...

String kan ook instanceof gebruiken omdat String final is. Anders zou hij anObject.getClass() moeten gebruiken, imho.
Als je de String klasse override en niet de value array gebruikt om de String in op te slaan, of op een andere manier, dan kan je niet meer controlleren of de waardes gelijk zijn.

"Beauty is the ultimate defence against complexity." David Gelernter


  • gsteen
  • Registratie: November 2004
  • Laatst online: 13-01-2020
Salandur schreef op woensdag 06 april 2005 @ 09:39:
Uit mijn voorbeeld blijkt dat dat niet altijd het geval is, want s1 en s2 zijn 2 verschillende objecten en allebei apart gedeclareerd. Toch worden ze bij '==' als gelijk aangeduid.
code:
1
2
String s1 = "Amsterdam";
String s2 = "Amsterdam";

Strings die op deze manier worden gedeclareerd worden in een soort 'pool of string' gegooit.
Bij declaratie van s1 wordt de string Amsterdam in deze pool gezet. Declareer je vervolgens s2 dan wordt er eerst in deze pool gekeken of de string al bestaat. Zo ja dan gaat s2 naar dezelfde locatie verwijzen als s1. Daarom geeft s1==s2 true terug.

Zou je het zo doen:
code:
1
2
String s1 = new String("Amsterdam");
String s2 = new String("Amsterdam");

Dan worden de strings niet in de pool geplaatst en zal s1==s2 nooit true opleveren.

"In theory, there is no difference between theory and practice. But, in practice, there is."


  • Salandur
  • Registratie: Mei 2003
  • Laatst online: 10:41

Salandur

Software Engineer

gsteen schreef op woensdag 06 april 2005 @ 16:17:
[...]


code:
1
2
String s1 = "Amsterdam";
String s2 = "Amsterdam";

Strings die op deze manier worden gedeclareerd worden in een soort 'pool of string' gegooit.
Bij declaratie van s1 wordt de string Amsterdam in deze pool gezet. Declareer je vervolgens s2 dan wordt er eerst in deze pool gekeken of de string al bestaat. Zo ja dan gaat s2 naar dezelfde locatie verwijzen als s1. Daarom geeft s1==s2 true terug.

Zou je het zo doen:
code:
1
2
String s1 = new String("Amsterdam");
String s2 = new String("Amsterdam");

Dan worden de strings niet in de pool geplaatst en zal s1==s2 nooit true opleveren.
Je hebt helemaal gelijk. Alleen zullen de meeste mensen niet zo snel een string aanmaken met new String(".."). Maar het verschil is wel verwarrend natuurlijk.

Assumptions are the mother of all fuck ups | iRacing Profiel

Pagina: 1