Path: csiph.com!eternal-september.org!feeder.eternal-september.org!mx02.eternal-september.org!.POSTED!not-for-mail From: Eric Sosman Newsgroups: comp.lang.java.programmer Subject: Re: Java primitive variable objects Date: Mon, 2 Nov 2015 14:15:59 -0500 Organization: A noiseless patient Spider Lines: 165 Message-ID: References: <33c95894-2638-4484-af5d-545701d34372@googlegroups.com> Mime-Version: 1.0 Content-Type: text/plain; charset=windows-1252; format=flowed Content-Transfer-Encoding: 7bit Injection-Date: Mon, 2 Nov 2015 19:13:48 -0000 (UTC) Injection-Info: mx02.eternal-september.org; posting-host="858a9d65d95f4ea4d355bd116aa2cdbf"; logging-data="20600"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX19xcKIGHH5oT/qVlQdftECT" User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Thunderbird/38.3.0 In-Reply-To: <33c95894-2638-4484-af5d-545701d34372@googlegroups.com> Cancel-Lock: sha1:ENqzFEOr3hUpszq9dR9ba/g64FY= Xref: csiph.com comp.lang.java.programmer:32972 On 11/2/2015 1:11 PM, Eric Douglas wrote: > I just discovered something which created a minor problem in my class. What would be the best way to solve it? > > I have some constant values defined such as this > public static final int AVALUE = 1; > > some methods in the class have conditions defined such as this > if (myValue == MyClass.AVALUE) { > > I'm passing in myValue as Integer with a possibility it could be null. > I hadn't tested these methods with the null value so I didn't realize it actually gets an error. ... as it should. "Unboxing" an Integer to extract its int value cannot proceed if the Integer reference is null, that is, if there's no Integer to be unboxed. > This code > Integer x = 1; > Integer o = 1; > if (x == o) { > System.out.println("equal"); > } else { > System.out.println("not"); > } > prints "equal". Right, but perhaps not for the reason you may imagine. The comparison is not made between the unboxed values of the Integer references, but is the traditional "reference identity" comparison. That is, the comparison checks whether the two references x and o refer to the same Integer instance. Java happens to guarantee that the Integer instances produced by "boxing" small int values are identical: It caches a bunch of small-valued Integer objects and uses them over and over. So if you box the int value 1 twice, you get two references to the same cached 1-valued Integer, and == pronounces them identical. If you box some int values whose corresponding Integers are *not* cached, you get two brand-new Integer instances that == will declare to be different. I forget what the mandatory range of cached values is (and individual implementations are allowed to cache more than are required), but if you experiment with changing the 1's in your example to -192837465's or some such, you may well get a different outcome. > This code > Integer x = new Integer(1); > Integer o = 1; > if (x == o) { > System.out.println("equal"); > } else { > System.out.println("not"); > } > prints "not". Right. The x reference is to a brand-new 1-valued Integer, while the o reference is to a 1-valued Integer in the JVM's cache. The == sees that these two are different objects, and says so. > This code > Integer x = new Integer(1); > int o = 1; > if (x == o) { > System.out.println("equal"); > } else { > System.out.println("not"); > } > prints "equal". Right, because comparison between an Integer and an int works by "unboxing" the Integer and comparing the two int values. The comparison is shorthand for `x.intValue() == o'. > This code > Integer x = null; > int o = 1; > if (x == o) { > System.out.println("equal"); > } else { > System.out.println("not"); > } > gets a NullPointerException on the if. As above, the comparison is `x.intValue() == o', and when x is null you can't call instance methods on it. > Of course Eclipse at this point knows x is null and shows a warning on the if, but if you break it out as the real code would be, it compiles without error and just crashes when you run. > > public static void main(String[] args) { > Integer x = null; > checkThis(x); > } > public static void checkThis(final Integer checkMe) { > int o = 1; > if (checkMe == o) { > System.out.println("equal"); > } else { > System.out.println("not"); > } > } > > Solutions? It's not clear to me just what problem you think needs solving. Boxing and unboxing conversions can be confusing, agreed, but once Sun decided to add them to Java there was no going back: You simply need to know what the rules are, or know how to stay away from the situations where the confusing bits arise. > 1) Change all the constants to objects > public static final Integer AVALUE = 1; Still plenty of room for surprises: if (new Integer(1) == AVALUE) ... ... and similarly for any other 1-valued Integer of unknown provenance. > 2) Add null checks to all conditions > if (checkMe != null && checkMe == o) { This avoids the NullPointerException, but as with (1) it can still yield surprises. (Also, although it reliably detects "present and equal" it fails to distinguish between "absent" and "present but unequal." In a real program, you'd probably want to treat the three cases differently.) > Is there a third option? [...] I'd recommend not comparing Integers (or Bytes, etc.) with the == and != operators. Comparing them with int (byte, ...) values is okay if the references are non-null, because unboxing occurs and you get a value-based comparison. It's also okay to compare non-null references with <, <=, >=, and >, because these also cause unboxing. To avoid nulls, try to use Integer (etc.) only when forced to and prefer to use int (etc.) wherever you can. When you must use an Integer -- for example, when you're storing values in a Collection -- be careful to null-check when you need to: Map occurrences = ...; for (Thing thing : collectionOfThings) { Integer count = occurrences.get(thing); occurrences.put(thing, count == null ? 1 : count + 1); } > [...] Is there a difference in declaring static int constants versus Integers for memory use, performance? There's certainly a difference in memory use. An int value takes four bytes, period. Using an Integer requires the Integer object itself plus a reference variable that points to it. On my system (yours may be different) that's sixteen bytes for the Integer and four more for the reference. As mentioned above, though, some smallish Integers are cached and re-used, so part of the twenty-byte total may be "free" or at the least "amortized;" it's hard to come up with an exact measurement of the int-to-Integer penalty. Performance? Dunno, but I suspect the difference is fairly small, probably too small to be measured without a Real Whole Lot of work. You could take it on as a research project, I guess. -- esosman@comcast-dot-net.invalid "Don't be afraid of work. Make work afraid of you." -- TLM