Java Puzzle
Java Puzzle
Java Puzzle
Filipp Shubin
Learn some of the quirks of programming in general and the Java language in particular; Have fun!
Learning Objectives
As a result of this presentation, you will be able to:
Avoid some common programming pitfalls Have some fun while you are learning
Another Look
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 class Dog { public static void bark() { System.out.print("woof "); } } class Basenji extends Dog { public static void bark() { } } public class Bark { public static void main(String args[]) { Dog woofer = new Dog(); Dog nipper = new Basenji(); woofer.bark(); nipper.bark(); } }
The Moral
Static methods can't be overridden
2. What's in a Name?
01 public class Name { 02 private String first, last; 03 public Name(String first, String last) { 04 this.first = first; 05 this.last = last; 06 } 07 public boolean equals(Object o) { 08 if (!(o instanceof Name)) return false; 09 Name n = (Name)o; 10 return n.first.equals(first) && 11 n.last.equals(last); 12 } 13 public static void main(String[] args) { 14 Set s = new HashSet(); 15 s.add(new Name("Donald", "Duck")); 16 System.out.println( 17 s.contains(new Name("Donald", "Duck"))); 18 } 19 }
Another Look
01 public class Name { 02 private String first, last; 03 public Name(String first, String last) { 04 this.first = first; 05 this.last = last; 06 } 07 public boolean equals(Object o) { 08 if (!(o instanceof Name)) return false; 09 Name n = (Name)o; 10 return n.first.equals(first) && 11 n.last.equals(last); 12 } 13 public static void main(String[] args) { 14 Set s = new HashSet(); 15 s.add(new Name("Donald", "Duck")); 16 System.out.println( 17 s.contains(new Name("Donald", "Duck"))); 18 } 19 }
The Moral
If you override equals, override hashCode Obey general contracts when overriding See Effective Java, Chapter 3
3. Indecision
01 class Indecisive { 02 public static void main(String[] args) { 03 System.out.println(waffle()); 04 } 05 06 static boolean waffle() { 07 try { 08 return true; 09 } finally { 10 return false; 11 } 12 } 13 }
Another Look
01 class Indecisive { 02 public static void main(String[] args) { 03 System.out.println(waffle()); 04 } 05 06 static boolean waffle() { 07 try { 08 return true; 09 } finally { 10 return false; 11 } 12 } 13 }
The Moral
Avoid abrupt completion of finally blocks
Wrap unpredictable actions with nested trys Don't return or throw exceptions
It relies on int subtraction Int too small to hold difference of 2 arbitrary ints
Another Look
01 public class SordidSort { 02 public static void main(String args[]) { 03 Integer big = new Integer( 2000000000); 04 Integer small = new Integer(-2000000000); 05 Integer zero = new Integer(0); 06 Integer[] a = new Integer[] {big,small,zero}; 07 Arrays.sort(a, new Comparator() { 08 public int compare(Object o1, Object o2) { 09 return ((Integer)o2).intValue() 10 ((Integer)o1).intValue(); 11 } 12 }); 13 System.out.println(Arrays.asList(a)); 14 } 15 }
The Moral
ints aren't integers! Think about overflow This particular comparison technique
OK only if max - min <= Integer.MAX_VALUE For example: all values positive
The Moral
At least one operand must be a String If it isn't, cast or convert" + 'H' + 'a');
Another Look
01 public class Confusing { 02 public Confusing(Object o) { 03 System.out.println("Object"); 04 } 05 public Confusing(double[] dArray) { 06 System.out.println("double array"); 07 } 08 public static void main(String args[]) { 09 new Confusing(null); 10 } 11 }
The Moral
Avoid overloading If you overload, avoid ambiguity If you do have ambiguous overloadings, make their behavior identical If you are using a "broken" class, make intentions clear with a cast
Another Look
01 public class ByteMe { 02 public static void main(String[] args) { 03 for (byte b = Byte.MIN_VALUE; 04 b < Byte.MAX_VALUE; b++) { 05 if (b == 0x90) // (b == 144) 06 System.out.print("Byte me! "); 07 } 08 } 09 } 10 11 // But (byte)0x90 == -112
The Moral
Bytes aren't ints Be careful when mixing primitive types Compare like-typed expressions
The Moral
Avoid float and double where exact answers are required Use BigDecimal, int, or long instead
9. A Private Matter
01 02 03 04 05 06 07 08 09 10 11 12 13 class Base { public String name = "Base"; } class Derived extends Base { private String name = "Derived"; } public class PrivateMatter { public static void main(String[] args) { System.out.println(new Derived().name); } }
Another Look
01 02 03 04 05 06 07 08 09 10 11 12 13 class Base { public String name = "Base"; } class Derived extends Base { private String name = "Derived"; } public class PrivateMatter { public static void main(String[] args) { System.out.println(new Derived().name); } }
The Moral
Avoid hiding
Another Look
01 public class Loopy { 02 public static void main(String[] args) { 03 final int start = Integer.MAX_VALUE 04 100; 05 final int end = Integer.MAX_VALUE; 06 int count = 0; 07 for (int i = start; i <= end; i++) 08 count++; 09 System.out.println(count); 10 } 11 }
The Moral
ints aren't integers! Think about overflow Use necessary
larger type if
Another Look
01 public class RandomSet { 02 public static void main(String[] args) { 03 Set s = new HashSet(); 04 for (int i=0; i<100; i++) 05 s.add(randomInteger()); 06 System.out.println(s.size()); 07 } 08 09 private static Integer randomInteger() { 10 return new Integer(new Random().nextInt()); 11 } 12 }
The Moral
Use one Random instance for each sequence In most programs, one is all you need In multithreaded programs, you may want multiple instances for increased concurrency
Seed explicitly or risk identical sequences Generally ok to use one instance to seed others
12.Making a Hash of It
01 public class Name { 02 private String first, last; 03 public Name(String first, String last) { 04 if (first == null || last == null) 05 throw new NullPointerException(); 06 this.first = first; this.last = last; 07 } 08 public boolean equals(Name o) { 09 return first.equals(o.first) && last.equals(o.last); 10 } 11 public int hashCode() { 12 return 31 * first.hashCode() + last.hashCode(); 13 } 14 public static void main(String[] args) { 15 Set s = new HashSet(); 16 s.add(new Name("Mickey", "Mouse")); 17 System.out.println( 18 s.contains(new Name("Mickey", "Mouse"))); 19 } 20 }
Another Look
01 public class Name { 02 private String first, last; 03 public Name(String first, String last) { 04 if (first == null || last == null) 05 throw new NullPointerException(); 06 this.first = first; this.last = last; 07 } 08 public boolean equals(Name o) { // Accidental overloading 09 return first.equals(o.first) && last.equals(o.last); 10 } 11 public int hashCode() { // Overriding 12 return 31 * first.hashCode() + last.hashCode(); 13 } 14 public static void main(String[] args) { 15 Set s = new HashSet(); 16 s.add(new Name("Mickey", "Mouse")); 17 System.out.println( 18 s.contains(new Name("Mickey", "Mouse"))); 19 } 20 }
The Moral
If you want to override a method:
Make sure signatures match The compiler doesnt check for you Do copy-and-paste declarations!
Another Look
01 class PingPong { 02 public static synchronized void main(String[] a) { 03 Thread t = new Thread() { 04 public void run() { 05 pong(); 06 } 07 }; 08 09 t.run(); // Common typo! 10 System.out.print("Ping"); 11 } 12 13 static synchronized void pong() { 14 System.out.print("Pong"); 15 } 16 }
The Moral
Invoke Thread.start, not Thread.run
Common error Can be very difficult to diagnose
14. Shifty
01 public class Shifty { 02 public static void main(String[] args) { 03 int distance = 0; 04 while ((-1 << distance) != 0) 05 distance++; 06 System.out.println(distance); 07 } 08 }
Another Look
01 public class Shifty { 02 public static void main(String[] args) { 03 int distance = 0; 04 while ((-1 << distance) != 0) 05 distance++; 06 System.out.println(distance); 07 } 08 } 09 10 // (-1 << 32) == -1
The Moral
Shift distances are computed mod 32 (or 64) Its impossible to shift out an entire int (or long) using any shift operator or distance Use care when shift distance is not a literal
The Moral
Unicode escapes are dangerous
Equivalent to the character they represent!
Another Look
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 public class Puzzling { public static void main(String[] args) { String s = new String("blah"); System.out.println(s); } } class String { java.lang.String s; public String(java.lang.String s) { this.s = s; } public java.lang.String toString() { return s; } }
The Moral
Avoid name reuse in all its guises
hiding, shadowing, overloading
Another Look
01 import java.lang.reflect.*; 02 03 public class Reflector { 04 public static void main(String[] args) throws Exception { 05 Set s = new HashSet(); 06 s.add("foo"); 07 Iterator i = s.iterator(); 08 Method m = 09 i.getClass().getMethod("hasNext", new Class[0]); 10 System.out.println(m.invoke(i, new Object[0])); 11 } 12 }
The Moral
Reflection has its own access rules Avoid reflection when possible If you must use reflection
Instantiate using reflection Cast to an interface type Access via interface
Another Look
01 public class StringCheese { 02 public static void main(String args[]) { 03 byte b[] = new byte[256]; 04 for(int i = 0; i < 256; i++) 05 b[i] = (byte)i; 06 String str = new String(b); 07 for(int i = 0; i < str.length(); i++) 08 System.out.print((int)str.charAt(i) + " "); 09 } 10 }
String(byte[] bytes) Constructs a new String by decoding the specified array of bytes using the platforms default charset. [from API Spec.]
The Moral
Converting bytes to chars uses a charset If you dont specify one, you get default
Depends on OS and locale
Another Look
01 // Static initialization proceeds top to bottom.
02 public class Elvis {
03
04 05 06 07 08 09 10 11 12 13 14 15 16 17 }
public static final Elvis INSTANCE = new Elvis(); private final int beltSize; private static final int CURRENT_YEAR = Calendar.getInstance().get(Calendar.YEAR);
private Elvis() { beltSize = CURRENT_YEAR - 1930; } public int beltSize() { return beltSize; } public static void main(String[] args) { System.out.println("Elvis wears size " + INSTANCE.beltSize() + " belt."); }
The Moral
Watch out for circularities in static initialization
One or more classes may be involved Circularities arent necessarily wrong but
" Constructors can run before class fully initialized " Static fields can be read before theyre initialized
Superclass constructor runs a subclass method before the subclass instance is initialized.
01 class Point { 02 protected final int x, y; 03 private final String name; 04 protected String makeName() { return "[" + x + "," + y + "]"; } 05 public final String toString() { return name; } 06 Point(int x, int y) { 07 this.x = x; this.y = y; 08 this.name = makeName(); // (3) Invokes subclass method 09 } 10 } 11 12 public class ColorPoint extends Point { 13 private final String color; 14 // (4) Subclass method executes before subclass constructor body! 15 protected String makeName() { return super.makeName() + ":" + color; } 16 ColorPoint(int x, int y, String color) { 17 super(x, y); // (2) Chains to superclass constructor 18 this.color = color; // (5) Initializes blank final instance field 19 } 20 public static void main(String[] args) { // (1) Invoke subclass cons. 21 System.out.println(new ColorPoint(4, 2, "purple")); 22 } 23 }
Another Look
01 class Point { 02 protected final int x, y; 03 private String name; // Lazily initialized (cached on first use) 04 protected String makeName() { return "[" + x + "," + y + "]"; } 05 public final synchronized String toString() 06 { return (name == null ? (name = makeName()) : name); } 07 Point(int x, int y) { 08 this.x = x; this.y = y; 09 // (name initialization removed) 10 } 11 } 12 13 public class ColorPoint extends Point { 14 private final String color; 15 protected String makeName() { return super.makeName() + ":" + color; } 16 ColorPoint(int x, int y, String color) { 17 super(x, y); 18 this.color = color; 19 } 20 public static void main(String[] args) { 21 System.out.println(new ColorPoint(4, 2, "purple")); 22 } 23 }
The Moral
Never call overridable methods from constructors, directly or indirectly Also applies to pseudo-constructors
readObject() clone()
Another Look
01 public class LongDivision { 02 private static final long MILLIS_PER_DAY 03 = 24 * 60 * 60 * 1000; 04 private static final long MICROS_PER_DAY 05 = 24 * 60 * 60 * 1000 * 1000; // >> Integer.MAX_VALUE 06 07 public static void main(String[] args) { 08 System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY); 09 } 10 }
The Moral
When working with large numbers watch
out for overflowits a silent killer
The program has three separate bugs. One of them is quite subtle.
Another Look
01 public class Rhymes { 02 private static Random rnd = new Random(); 03 public static void main(String[] args) { 04 StringBuffer word = null; 05 switch(rnd.nextInt(2)) { // No breaks! 06 case 1: word = new StringBuffer('P'); 07 case 2: word = new StringBuffer('G'); 08 default: word = new StringBuffer('M'); 09 } 10 word.append('a'); 11 word.append('i'); 12 word.append('n'); 13 System.out.println(word); 14 } 15 }
01 public class Rhymes { 02 private static Random rnd = new Random(); 03 public static void main(String[] args) { 04 StringBuffer word = null; 05 switch(rnd.nextInt(3)) { 06 case 1: word = new StringBuffer("P"); break; 07 case 2: word = new StringBuffer("G"); break; 08 default: word = new StringBuffer("M"); break; 09 } 10 word.append('a'); 11 word.append('i'); 12 word.append('n'); 13 System.out.println(word); 14 } 15 }
The Moral
Use common idioms
If you must stray, consult the documentation
Chars are not strings; theyre more like ints Always remember breaks in switch
statement
Watch out for fence-post errors Watch out for sneaky puzzlers
Another Look
01 public class NameGame { 02 public static void main(String args[]) { 03 Map m = new IdentityHashMap(); 04 m.put("Mickey", "Mouse"); 05 m.put("Mickey", "Mantle"); 06 System.out.println(m.size()); 07 } 08 }
The Moral
IdentityHashMap not a general-purpose
Map Dont use it unless you know its what you want Uses identity in place of equality Useful for topology-preserving transformations
No programmer-defined constructor
Another Look
01 public class Names { 02 private Map m = new HashMap(); 03 public void Names() { // Not a constructor! 04 m.put("Mickey", "Mouse"); 05 m.put("Mickey", "Mantle"); 06 } 07 08 public int size() { return m.size(); } 09 10 public static void main(String args[]) { 11 Names names = new Names(); // Invokes default! 12 System.out.println(names.size()); 13 } 14 }
The Moral
It is possible for a method to have
the same name as a constructor
Dont ever do it
Obey naming conventions
field, method(), Class, CONSTANT
Another Look
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 public class Gray { public static void main(String[] args){ System.out.println(X.Y.Z); } } class X { static class Y { static String Z = "Black"; } static C Y = new C(); } class C { String Z = "White"; }
The Moral
Obey naming conventions
field, method(), Class, CONSTANT Single-letter uppercase names reserved for type variables (new in J2SE 1.5)
Another Look
01 public class Elementary { 02 public static void main(String[] args) { 03 04 05 } } System.out.println(5432
+ 5432
l);
1 l
The Moral
Always use uppercase el (L) for long
literals
Lowercase el makes the code unreadable 5432L is clearly a long, 5432l is misleading
Another Look
01 public class Count { 02 public static void main(String[] args) { 03 final int START = 2000000000; 04 int count = 0; 05 for (float f = START; f < START + 50; f++) 06 count++; 07 System.out.println(count); 08 } 09 }
The Moral
Dont use floating point for loop
indices Not every int can be expressed as a float Not every long can be expressed as a double
Another Look
01 public class Classifier { 02 public static void main(String[] args) { 03 System.out.println( 04 classify('n') + classify('+') + classify('2')); 05 } 06 static String classify(char ch) { 07 if ("0123456789".indexOf(ch) >= 0) 08 return "NUMERAL "; 09 if ("abcdefghijklmnopqrstuvwxyz".indexOf(ch) >= 0) 10 return "LETTER "; 11 /* (Operators not supported yet) 12 * if ("+-*/&|!=".indexOf(ch) >= 0) 13 * return "OPERATOR "; 14 */ 15 return "UNKNOWN "; 16 } 17 }
The Moral
You cannot reliably block-comment
out code
Comments do not nest
Another Look
01 public class JoyOfHex { 02 public static void main(String[] args) { 03 System.out.println( 04 Long.toHexString(0x100000000L + 0xcafebabe)); 05 } 06 }
1 1 1 1 1 1 1
The Moral
Decimal literals are all positive; not so for hex
> Negative decimal constants have minus
sign > Hex literals are negative if the high-order bit is set
Widening conversion can cause sign extension Mixed-mode arithmetic is tricky avoid it
Another Look
01 public class AnimalFarm { 02 public static void main(String[] args) { 03 final String pig = "length: 10"; 04 final String dog = "length: "+pig.length(); 05 System.out.println("Animals are equal: " 06 + pig == dog); 07 } 08 }
The Moral
Parenthesize when using string concatenation
Spacing can be deceptive; parentheses never lie
Dont depend on interning of string constants Use equals, not ==, for strings
Operands are evaluated left to right. Postfix increment returns old value.
Another Look
01 public class Assignment { 02 public static void main(String[] a) throws Exception { 03 int tricky = 0; 04 for (int i = 0; i < 3; i++) 05 tricky += tricky++; 06 System.out.println(tricky); 07 } 08 }
Another Look
01 public class Assignment { 02 public static void main(String[] a) throws Exception { 03 int tricky = 0; 04 for (int i = 0; i < 3; i++) 05 tricky += tricky++; 06 System.out.println(tricky); 07 } 08 }
Another Look
05
(tricky == 0)
Another Look
05
(tricky == 0)
Another Look
05
tricky += tricky++; 0 0
(tricky == 1)
Another Look
05
tricky += tricky++; 0 0
(tricky == 1)
Another Look
05
tricky += tricky++; 0 0
(tricky == 0)
Another Look
01 public class Assignment { 02 public static void main(String[] a) throws Exception { 03 int tricky = 0; 04 for (int i = 0; i < 3; i++) 05 tricky += tricky++; 06 System.out.println(tricky); 07 } 08 }
The Moral
Dont depend on details of expression evaluation Dont assign to a variable twice in one expression Postfix increment returns old value (Operands are evaluated left to right)
Another Look
01 public class Loop { 02 public static void main(String[] args) { 03 int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 }, 04 { 1, 2, 3 }, { 1, 2, 3, 4 }, { 1 } }; 05 int successCount = 0; 06 try { 07 int i = 0; 08 while (true) { 09 if (thirdElementIsThree(tests[i++])) 10 successCount++; 11 } 12 } catch (ArrayIndexOutOfBoundsException e) { } 13 System.out.println(successCount); 14 } 15 private static boolean thirdElementIsThree(int[] a) { 16 return a.length >= 3 & a[2] == 3; 17 } 18 }
The Moral
Use exceptions only for exceptional conditions
> Never use exceptions for normal
control flow
on boolean
Another Look
01 class Cache { 02 static { initIfNecessary(); } 03 private static int sum; 04 public static int getSum() { 05 initIfNecessary(); 06 return sum; 07 } 08 private static boolean initialized = false; 09 private static synchronized void initIfNecessary() { 10 if (!initialized) { 11 for (int i = 0; i < 100; i++) 12 sum += i; 13 initialized = true; 14 } 15 } 16 public static void main(String[] args) { 17 System.out.println(getSum()); 18 } 19 }
Another Look
01 class Cache { 02 static { initIfNecessary(); } 03 private static int sum; 04 public static int getSum() { 05 initIfNecessary(); 06 return sum; 07 } 08 private static boolean initialized = false; // Ouch! 09 private static synchronized void initIfNecessary() { 10 if (!initialized) { 11 for (int i = 0; i < 100; i++) 12 sum += i; 13 initialized = true; 14 } 15 } 16 public static void main(String[] args) { 17 System.out.println(getSum()); 18 } 19 }
The Moral
Use eager or lazy initialization, not both
> Prefer eager initialization to lazy
Another Look
01 public class Mod { 02 public static void main(String[] args) { 03 final int MODULUS = 3; 04 int[] histogram = new int[MODULUS]; 05 06 int i = Integer.MIN_VALUE; 07 // This loop iterates over all int values 08 do { 09 histogram[Math.abs(i) % MODULUS]++; 10 } while (i++ != Integer.MAX_VALUE); 11 12 for (int j = 0; j < MODULUS; j++) 13 System.out.println(histogram[j] + " "); 14 } 15 }
With:
histogram[mod(i, MODULUS)]++; private static int mod(int i, int modulus) { int result = i % modulus; return result < 0 ? result + modulus : result; }
The Moral
Math.abs can return a negative value
Twos-complement integers are asymmetric int arithmetic overflows silently
i mod m Math.abs(i) % m
Another Look
01 package click; 02 public class CodeTalk { 03 public void doIt() { printMessage(); } 04 void printMessage() { System.out.println("Click"); } 05 } ___________________________________________________________ 01 package hack; 02 import click.CodeTalk; 03 public class TypeIt { 04 private static class ClickIt extends CodeTalk { 05 void printMessage() { System.out.println("Hack"); } 06 } 07 public static void main(String[] args) { 08 new ClickIt().doIt(); 09 } 10 }
The Moral
Package-private methods cant be overridden by methods outside their package If you cant see it, you cant override it
Another Look
01 public class Lazy { 02 private static boolean initialized = false; 03 static { 04 Thread t = new Thread(new Runnable() { 05 public void run() { 06 initialized = true; // Deadlocks here! 07 } 08 }); 09 t. start(); 10 try { 11 t.join(); 12 } catch (InterruptedException e) { 13 throw new AssertionError(e); 14 } 15 } 16 public static void main(String[] args) { 17 System.out.println(initialized); 18 } 19 }
The Moral
Never use background threads in class initialization
Another Look
public class OddBehavior { public static void main(String[] args) { List<Integer> list = Arrays.asList(-2, -1, 0, 1, 2); boolean foundOdd = false; for (Iterator<Integer> it = list.iterator(); it.hasNext(); foundOdd = foundOdd || isOdd(it.next()); } System.out.println(foundOdd);
The Moral
Use for-each wherever possible
> Nicer and safer than explicit iterator or
index usage
If you must use an iterator, make sure you call next() exactly once Conditional operators evaluate their right operand only if necessary to determine result
> This is almost always what you want
(a) [-3, -2, -1] [-3, -2, -1] (b) [-3, -2, -1] [-2, 0, 2] (c) Throws exception (d) None of the above
public class SetList { public static void main(String[] args) { Set<Integer> set = new LinkedHashSet<Integer>(); List<Integer> list = new ArrayList<Integer>(); for (int i = -3; i < 3; i++) { set.add(i); list.add(i); } for (int i = 0; i < 3; i++) { set.remove(i); list.remove(i); } System.out.println(set + " " + list);
Another Look
public class SetList { public static void main(String[] args) { Set<Integer> set = new LinkedHashSet<Integer>(); List<Integer> list = new ArrayList<Integer>(); for (int i = -3; i < 3; i++) { set.add(i); list.add(i); } for (int i = 0; i < 3; i++) { set.remove(i); list.remove(i); // List.remove(int) } System.out.println(set + " " + list);
The Moral
Avoid ambiguous overloadings Harder to avoid in release 5.0
> Autoboxing, varargs, generics
public enum PowerOfTen { (c) one ten 100 ONE(1), TEN(10), HUNDRED(100) { (d) None of the above @Override public String toString() { return Integer.toString(val); } }; private final int val; PowerOfTen(int val) { this.val = val; } @Override public String toString() { return name().toLowerCase(); } public static void main(String[] args) { System.out.println(ONE + " " + TEN + " " + HUNDRED); }
Another Look
public enum PowerOfTen { ONE(1), TEN(10), HUNDRED(100) { // Creates static anonymous class @Override public String toString() { return Integer.toString(val); } }; private final int val; PowerOfTen(int val) { this.val = val; } @Override public String toString() { return name().toLowerCase(); } public static void main(String[] args) { System.out.println(ONE + " " + TEN + " " + HUNDRED); }
The Moral
Nest-mates can use each others private members But private members are never inherited Constant-specific enum bodies define static anonymous classes Compiler diagnostics can be confusing
(d) None of the above @interface Test { } public class Testy { @Test public static void test() { return; } @Test public static void test2() { new RuntimeException(); } public static void main(String[] args) throws Exception { for (Method m : Testy.class.getDeclaredMethods()) { if (m.isAnnotationPresent(Test.class)) { try { m.invoke(null); System.out.print("Pass "); } catch (Throwable ex) { System.out.print("Fail "); } } } } }
Another Look
import java.lang.reflect.*; @interface Test { } // By default, annotations are discarded at runtime public class Testy { @Test public static void test() { return; } @Test public static void test2() { new RuntimeException(); } // Oops ! public static void main(String[] args) throws Exception { for (Method m : Testy.class.getDeclaredMethods()) { if (m.isAnnotationPresent(Test.class)) { try { m.invoke(null); System.out.print("Pass"); } catch (Throwable ex) { System.out.print("Fail "); } } } } }
The Moral
By default, annotations are discarded at runtime
> If you need annotations at runtime, use
@Retention(RetentionPolicy.RUNTIME )
> If you want them omitted from class file,
use @Retention(RetentionPolicy.SOURCE)
(a) Bleep
(b) Blat public class Bleep { String name = "Bleep"; (c) It varies void setName(String name) { this.name = name; (d) None of the above } void backgroundSetName() throws InterruptedException { Thread t = new Thread() { @Override public void run() { setName("Blat"); } }; t.start(); t.join(); System.out.println(name); } public static void main(String[] args) throws InterruptedException { new Bleep().backgroundSetName(); } }
Another Look
public class Bleep { String name = "Bleep"; void setName(String name) { // Does this look familiar? this.name = name; } void backgroundSetName() throws InterruptedException { Thread t = new Thread() { // Invokes Thread.setName (shadowing) @Override public void run() { setName("Blat"); } }; t.start(); t.join(); System.out.println(name); } public static void main(String[] args) throws InterruptedException { new Bleep().backgroundSetName(); } }
The Moral
Dont extend Thread
> Use new Thread(Runnable) instead
information
Beware of shadowing
(a) true (b) false (c) Throws exception (d) None of the above
Another Look
public class BeyondCompare { public static void main(String[] args) { Object o = new Integer(3); System.out.println(new Double(3).compareTo(o) == 0); } } // Interface Comparable was generified in release 5.0 public interface Comparable<T> { int compareTo(T t); // Was Object } public class Double extends Number implements Comparable<Double>
The Moral
Binary compatibility is preserved at all costs Source compatibility broken for good cause (rare)
Comparable<T> alerts you to errors at compile time
(a) [1, 1, 2, 3, 5, 8, 13] (b) Throws exception (c) It varies (d) None of the above
Another Look
public class Fibonacci { private static final int LENGTH = 7; public static void main(String[] args) { int[] fib = new int[LENGTH]; fib[0] = fib[1] = 1; // First 2 Fibonacci numbers for (int i = 2; i < LENGTH; i++) fib[i] = fib[i - 2] + fib[i - 1]; // Idiom only works for arrays of object references System.out.println(Arrays.asList(fib)); } }
The Moral
Use varargs sparingly in your APIs
> It can hide errors and cause confusion > This program wouldn't compile under 1.4
obsolete
> use Arrays.toString instead > Prettier, safer, and more powerful
public class Parsing { /** * Returns Integer corresponding to s, or null if s is null. * @throws NumberFormatException if s is nonnull and * doesn't represent a valid integer */ public static Integer parseInt(String s) { return (s == null) ? (Integer) null : Integer.parseInt(s); } public static void main(String[] args) { System.out.println(parseInt("-1") + " " + parseInt(null) + " " + parseInt("1")); }
(a) -1 null 1 (b) -1 0 1 (c) Throws exception (d) None of the above
Another Look
public class Parsing { /** * Returns Integer corresponding to s, or null if s is null. * @throws NumberFormatException if s is nonnull and * doesn't represent a valid integer. */ public static Integer parseInt(String s) { return (s == null) ? // Mixed-type computation: Integer and int (Integer) null : Integer.parseInt(s); } public static void main(String[] args) { System.out.println(parseInt("-1") + " " + parseInt(null) + " " + parseInt("1")); } }
The Moral
Mixed-type computations are confusing Especially true for ?: expressions Avoid null where possible Auto-unboxing and null are a dangerous mix
Resources
Send more puzzles
puzzlers@javapuzzles.com
Conclusion
But it has a few sharp corners avoid them! Avoid name reuse: overloading, hiding, shadowing
If you aren't sure what a program does, it probably doesn't do what you want it to