Сообщений 1 Оценка 34 Оценить |
How well do you really know Java? Are you a code sleuth? Have you ever spent days chasing a bug caused by a trap or pitfall in Java or its libraries? Do you like brainteasers? Then this is the book for you!
In the tradition of Effective Java™, Bloch and Gafter dive deep into the subtleties of the Java programming language and its core libraries. Illustrated with visually stunning optical illusions, Java™ Puzzlers features 95 diabolical puzzles that educate and entertain. Anyone with a working knowledge of Java will understand the puzzles, but even the most seasoned veteran will find them challenging.
Most of the puzzles take the form of a short program whose behavior isn't what it seems. Can you figure out what it does? Puzzles are grouped loosely according to the features they use, and detailed solutions follow each puzzle. The solutions go well beyond a simple explanation of the program's behavior--they show you how to avoid the underlying traps and pitfalls for good. A handy catalog of traps and pitfalls at the back of the book provides a concise taxonomy for future reference.
Solve these puzzles and you'll never again fall prey to the counterintuitive or obscure behaviors that can fool even the most experienced programmers.
На мой взгляд книга стоит того, чтобы прочитать. В ней предлагаются 95 задач, сортированных по разделам, с объяснениями правильных ответов. С частью задач я сталкивался ранее, на часть давал неправильный ответ, больше всего было тех, которые заставляли поломать голову, чтобы найти верное решение. Мне нравится ломать голову над интересными задачками, мне понравилась эта книга. Кроме того, приятно почитать хорошо изложенное объяснение специалиста об известных вещах(problems of silent overflow, mixed computations etc).
Напоследок хочется привести пример паззла, чтобы дать представление о материале книги и о его подаче.
This program implements a simple linked list data structure. The main program builds a list with two elements and dumps its contents. What does the program print?
public class LinkedList<E> { private Node<E> head = null; private class Node<E> { E value; Node<E> next; // Node constructor links the node as a new head Node(E value) { this.value = value; this.next = head; head = this; } } public void add(E e) { new Node<E>(e); // Link node as new head } public void dump() { for (Node<E> n = head; n != null; n = n.next) System.out.print(n.value + " "); } public static void main(String[] args) { LinkedList<String> list = new LinkedList<String>(); list.add("world"); list.add("Hello"); list.dump(); } } |
Solution 89: Generic Drugs
Again, this program appears reasonably straightforward. New elements are added to the head of the list and the dump method prints the list starting with the head. Therefore, elements are printed in the opposite order they are added. In this case, the program first adds "world" and then "Hello", so it looks as if it is just a convoluted Hello world program. Sadly, if you tried to compile it, you found that it doesn't compile. The error messages from the compiler are downright baffling:
LinkedList.java:11: incompatible types found : LinkedList<E>.Node<E> required: LinkedList<E>.Node<E> this.next = head; ^ LinkedList.java:12: incompatible types found : LinkedList<E>.Node<E> required: LinkedList<E>.Node<E> head = this; ^ |
It appears that the compiler is complaining that a type isn't compatible with itself! Appearances, as usual, are deceiving. The "found" and "required" types are unrelated to each other. They appear identical because the program uses the same name to refer to different types. Specifically, the program contains two different declarations for type parameters named E. The first is the type parameter for LinkedList, and the second is the type parameter for the inner class LinkedList.Node. The latter shadows the former within the inner class. The lesson that we learned in Puzzles 71, 73, and 79 applies here as well: Avoid shadowing type parameter names.
There is no way to refer to a type parameter except by its simple name, so the error message has no way to tell you that these two uses of the name E refer to different types. The error message would be clearer if we systematically renamed the type parameter for Node from E to, say, T. It wouldn't fix the problem, but it would shed some light on it. This approach yields the following error messages:
LinkedList.java:11: inco>mpatible types found : LinkedList<E>.Node<E> required: LinkedList<E>.Node<T> this.next = head; ^ LinkedList.java:12: incompatible types found : LinkedList<E>.Node<T> required: LinkedList<E>.Node<E> head = this; ^ |
What the compiler is trying to tell us is that the program is way too complicated. An inner class of a generic class has access to the type parameters of its outer class. It was the clear intent of the program's author that the type parameter for a Node would always be the same as for the enclosing LinkedList, so there is no reason for Node to have a type parameter of its own. To fix the program, simply eliminate the type parameter in the inner class:
// Fixed but could be MUCH better public class LinkedList<E> { private Node head = null; private class Node { E value; Node next; // Node constructor links the node as a new head Node(E value) { this.value = value; this.next = head; head = this; } } public void add(E e) { new Node(e); // Link node as new head } public void dump() { for (Node n = head; n != null; n = n.next) System.out.print(n.value + " "); } } |
This is the simplest change that fixes the program, but it is not the best. The original program used an inner class unnecessarily. As mentioned in Puzzle 80, you should prefer static member classes over nonstatic [EJ Item 18]. An instance of LinkedList.Node contains not only the value and next fields but also a hidden field containing a reference to the enclosing LinkedList instance.
Although the enclosing instance is used during construction to read and then modify head, it is dead weight once construction has completed. Worse, placing the side effect of changing head into the constructor makes the program confusing to the reader. Change instance fields of a class only in its own instance methods.
A better fix, then, is to modify the original program to move the manipulation of head into LinkedList.add, making Node a static nested class rather than a true inner class. Static nested classes do not have access to the type parameters of enclosing classes, so now Node really does need a type parameter of its own. The resulting program is simple, clear, and correct:
class LinkedList<E> { private Node<E> head = null; private static class Node<T> { T value; Node<T> next; Node(T value, Node<T> next) { this.value = value; this.next = next; } } public void add(E e) { head = new Node<E>(e, head); } public void dump() { for (Node<E> n = head; n != null; n = n.next) System.out.print(n.value + " "); } } |
In summary, inner classes of generic classes have access to the enclosing class's type parameters, which can be confusing. The misunderstanding illustrated in this puzzle is common among programmers first learning generics. It isn't necessarily wrong to have an inner class in a generic class, but the need for this is rare, and you should consider refactoring your code to avoid it. When you have one generic class nested inside another, give their type parameters different names, even if the nested class is static. For language designers, perhaps it makes sense to forbid shadowing of type parameters, in the same way that shadowing of local variables is forbidden. Such a rule would have caught the bug in this puzzle.
Сообщений 1 Оценка 34 Оценить |