440 likes | 458 Views
Java Gotcha's. By Rick Mercer with help from the book: Java™ Puzzlers: Traps, Pitfalls, and Corner Cases Joshua Bloch and Neal Gafter. Every programming language has its quirks. Are you a code sleuth?
E N D
Java Gotcha's By Rick Mercer with help from the book: Java™ Puzzlers: Traps, Pitfalls, and Corner Cases Joshua Bloch and Neal Gafter
Every programming language has its quirks. • Are you a code sleuth? • Have you ever spent days chasing a bug caused by a trap or pitfall in Java or its libraries? • Here are some diabolical puzzles
Oddity • // Does this method return true if n is odd? • publicboolean isOdd(int n) { • return n % 2 == 1; • }
Ö • Yes, but it's wrong when n is negative • // Try this • return n % 2 != 0; • // or use bitwise AND operation (faster) • return (n & 1) == 1; • // n & 1 ands the rightmost bit with 1 • // if n is 25, n & 1 is 1 • // 00011001 & 00000001 is 00000001 • // Expression is 0 unless n is odd • // if n is 24, n & 1 is 0 • // 00011000 & 00000001 is 00000000
Simple Arithmetic • @Test • publicvoid simpleArithmetic() { • // Does this assertion pass? • assertEquals(444, 123 + 32l); • }
Ö • Eyes Deceive • It is 123 + 32L, which is 155 • Use L instead of l for Long
The laughs are on me • @Test • publicvoid simpleChars() { • // Which, if any, of these two assertions pass? • assertEquals("Ha", "H" + "a"); // a. • assertEquals("Ha", 'H' + 'a'); // b. • }
Ö • Answer • Only a. • java.lang.AssertionError: • expected:<Ha> but was:<169>
From a 227 Student // Part of escape obstacle course in findExit if(escape = false) findExit(r-1, c); // row above if(escape = false) findExit(r, c+1); // col to the right • Can this recursive solution ever work?
Ö • No • The boolean expressions are ALWAYS false • An assignment statement evaluates to the value of the right value (expression to the right of =) • What is the value of this expression? • booleanVar = true
From a 127B Student @Test publicvoidtestRecursion() { assertEquals(6, sumInts(3)); } intsumInts(int n) { if(n <= 1) return n; else return n * sumInts(n--); } • Can this recursive solution ever work?
Ö • No • n is not decremented until after the function call • f(n--)results in a StackOverflowError • Use f(n-1)
Output from this program? • \u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020 • \u0063\u006c\u0061\u0073\u0073\u0020\u0055\u0067\u006c\u0079 • \u007b\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020 • \u0020\u0020\u0020\u0020\u0073\u0074\u0061\u0074\u0069\u0063 • \u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028 • \u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u0020\u0020 • \u0020\u0020\u0020\u0020\u0061\u0072\u0067\u0073\u0029\u007b • \u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074 • \u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0020 • \u0022\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u0022\u002b • \u0022\u006f\u0072\u006c\u0064\u0022\u0029\u003b\u007d\u007d
Ö • Answer: • Hello World • \u0070 in hexadecimal is 112 in decimal or the character 'p' • Unicode not very readable • Suggestion" avoid Unicode till you need it • System.out.println(123.45 + " \u20ac" ); • 123.45 €
assertEquals('@', '\u0040'); assertEquals('A', '\u0041'); assertEquals('B', '\u0042'); assertEquals('`', '\u0060'); assertEquals('a', '\u0061'); assertEquals('b', '\u0062'); assertEquals('€', '\u20ac');
Is “true” true? • @Test • publicvoid trueOr() { • // Does this assertion pass? • assertEquals("Compare 5 to 4", "true", 5 > 4); • }
Ö • Answer • No • "true" is not true • However, JUnit show this: • java.lang.AssertionError: • Compare 5 to 4 expected:<true> but was:<true>
Output? • @Test • publicvoid whoopsForgotToBreak() { • int choice = 2; • switch (choice) { • case 1: • System.out.println("one"); • case 2: • System.out.println("two"); • case 3: • System.out.println("three"); • } • }
Ö • two • three • Add breaks • int choice = 2; • switch (choice) { • case 1: • System.out.println("one"); • break; • case 2: • System.out.println("two"); • break; • case 3: • System.out.println("three"); • break; • }
BTW: • Since Java 7 String allowed in switch • String choice = "2"; • switch (choice) { • case"1": • System.out.println("one"); • break; • case"2": • System.out.println("two"); • break; • case"3": • System.out.println("three"); • break; • }
Java plus plus • @Test • publicvoid thatDarnedPostIncrement() { • int j = 0; • for (int i = 0; i < 10; i++) • j = j++; • // Does this assertion pass? • assertEquals(10, j); • }
Ö • Answer: No • j = j++; is postfix increment operator • j is 0 after the loop • When you use a postfix operator as part of a larger expression, the expression's value is returned before the postfix operator is processed • the assignment completes before the increment • Use ++j; instead
Output? • int j = 0; • int k = 0; • System.out.println(j++); //? _____ • System.out.println(++k); //? _____ • System.out.println(j); //? _____ • int[] x = { 5, 4, 3 }; • int i = 0; • System.out.println(i + " " + x[i++]); //? _____ • System.out.println(i + " " + x[i]); //? _____ • System.out.println(i + " " + x[++i]); //? _____
O • 0 • 1 • 1 • With array • 0 5 • 1 4 • 1 3
Is there any Output? • publicclass Huh { • publicstaticvoid main(String[] args) { • new B(); • } • } • class B { • intj; • String s; • { • System.out.println("Hello world " + j + " " + s); • } • }
Ö • Answer: Yes • Hello world 0 null • This is an initializer, a method with no heading; • { }
Add to 0 three times • @Test • publicvoid testBigInt() { • BigInteger five = new BigInteger("5"); • BigInteger fifty = new BigInteger("50"); • BigInteger fiveHundred = new BigInteger("500"); • BigInteger total = BigInteger.ZERO; • total.add(five); • total.add(fifty); • total.add(fiveHundred); • // Does this assertion pass? • assertEquals(555, total); • }
Ö • No • BigInteger, like String is immutable • This will pass • BigInteger total = BigInteger.ZERO; • total = total.add(five); • total = total.add(fifty); • total = total.add(fiveHundred); • // Does this assertion pass • assertEquals(555, total);
No Warning • @Test • publicvoid testHashMap() { • HashMap<String, BigInteger> hm = • new HashMap<String, BigInteger>(); • hm.put("a", new BigInteger("123456")); • hm.put("b", new BigInteger("1234567")); • hm.put("c", new BigInteger("1234567")); • hm.put("a", new BigInteger("654321")); • BigInteger aBigInt = hm.get("a"); • // Does this assertion pass? • assertEquals(123456, aBigInt.intValue()); • }
Ö • No, when a key exists, the value gets replaced • // Return old value if the key exists • // return null if there was no mapping to the key • hm.put("a", new BigInteger("123456")); • BigInteger bi = hm.put("a", new BigInteger("9999")); • // put returned the old value mapped to the key "a" • assertEquals(123456, bi.intValue());
Output? • int n = 0; • try { • n = n / 0; // If double, n becomes NaN • } catch (Exception e) { • System.out.println("A"); • } finally { • System.out.println("B"); • } • System.out.println("C");
Ö • A • B • C • Finally blocks always execute unless System.exit(0) is encountered first
Output? • int n = 0; • try { • n = n / 999; • } catch (Exception e) { • System.out.println("A"); • } finally { • System.out.println("B"); • } • System.out.println("C");
Ö • B • C
Output • int n = 0; • try { • n = n / 999; • } catch (Exception e) { • System.out.println("A"); • System.exit(0); • } finally { • System.out.println("B"); • } • System.out.println("C");
Ö • B • C
Output • int n = 0; • try { • n = n / 0; • } catch (Exception e) { • System.out.println("A"); • System.exit(0); • } finally { • System.out.println("B"); • } • System.out.println("C");
Ö • A
String data = new String("123"); String moreData = new String("123"); System.out.println(data==moreData); To intern or not to intern Which assertion(s) pass? 1 and 2 1 only 2 only Neither @Test publicvoidstringIntern() { String s1 = "UofA"; String s2 = new String("UofA"); assertTrue(s1.equals(s2)); // 1 assertTrue(s1 == s2); // 2 }
Ö b) 1 only • == compares reference values, with new, a new string is created. Without new, Java tries to find the characters in the string pool where all instances of String are stored. If found, Java returns a reference to the existing instance • In Java, String is a Flyweight • to save memory • Both of these assertions pass: String s1 = "UofA"; String s2 = "UofA"; assertTrue(s1.equals(s2)); // 1 assertTrue(s1 == s2); // 2
To intern or not to intern? • @Test • publicvoid testIntegerInterns() { • Integer a = -128; • Integer b = -128; • Integer c = 127; • Integer d = 127; • Integer e = 345; • Integer f = 345; • // Which, if any, of these assertions fail? • assertTrue(a == b); // a. • assertTrue(c == d); // b. • assertTrue(e >= f); // c. • assertTrue(e == f); // d. • }
Ö • d only • == compares reference values so you would think all 3 fail, but.... • java.sun.com/docs/books/jls/download/langspec-3.0.pdf explicitly states that wrappers for values in the range -128 to 127 will be interned by any JVM. If you use the int literal, you get a reference to that existing instance • Flyweight: Do not create a new Integer(1), just return a reference to that instance from the existing pool of Integers -128..127 • to save memory
Are Doubles interned? • @Test • publicvoid testEqualEquals() { • Double a = 4.2; • Double b = 4.2; • // Which, if any, of these assertions fail? • assertTrue(a >= b); // a. • assertTrue(a <= b); // b. • assertTrue(a.equals(b)); // c. • assertTrue(a == b); // d. • assertTrue(a.compareTo(b) == 0); // e. • assertTrue(a.compareTo(4.2) == 0); // f. • }
Ö assertTrue(a == b); // d. fails • The == compares reference values, not the numeric values • Why does a == b evaluate to false? • There is no Double pool • a and b refer to two different objects with the same value • == compares references, not the 4.2s