Docsity
Docsity

Prepara tus exámenes
Prepara tus exámenes

Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity


Consigue puntos base para descargar
Consigue puntos base para descargar

Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium


Orientación Universidad
Orientación Universidad


Rompecabezas en Java, Apuntes de Programación Java

Resolución de ejercicios en Java

Tipo: Apuntes

2020/2021

Subido el 13/07/2021

emanuel-guadamuz
emanuel-guadamuz 🇵🇦

2 documentos

1 / 28

Toggle sidebar

Esta página no es visible en la vista previa

¡No te pierdas las partes importantes!

bg1
1
A
Java Puzzler
s Sampler
This sampler contains one puzzle from each chapter of Java Pu zzlers by Joshua
Bloch and Neal Gafter (Addison Wesley, 2005). The book is filled with brainteas-
ers about the Java programming language and its core libraries. Anyone with a
working knowledge of Java can understand these puzzles, but many of them are
tough enough to challenge even the most experienced programmer.
Most of the puzzles exploit counterintuitive or obscure behaviors that can lead
to bugs. These behaviors are known as traps, pitfalls, and corner cases. Every
platform has them, but Java has far fewer than other platforms of comparable
power. The goal of the book is to entertain you with puzzles while teaching you to
avoid the underlying traps and pitfalls. By working through the puzzles, you will
become less likely to fall prey to these dangers in your code and more likely to
spot them in code that you are reviewing or revising.
The book is meant to be read with a computer at your side. You’ll need a Java
development environment, such as Sun’s JDK. It should support release 5.0, as
some puzzles rely on features introduced in this release.
Most of the puzzles take the form of a short program that appears to do one
thing but actually does something else. That’s why we’ve chosen to decorate the
book with optical illusions—drawings that appear to be one thing but are actually
another. It’s your job to figure out each the program does. To get the most out of
these puzzles, we recommend that you take this approach:
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c

Vista previa parcial del texto

¡Descarga Rompecabezas en Java y más Apuntes en PDF de Programación Java solo en Docsity!

A Java Puzzlers Sampler

This sampler contains one puzzle from each chapter of Java Puzzlers by Joshua Bloch and Neal Gafter (Addison Wesley, 2005). The book is filled with brainteas- ers about the Java programming language and its core libraries. Anyone with a working knowledge of Java can understand these puzzles, but many of them are tough enough to challenge even the most experienced programmer. Most of the puzzles exploit counterintuitive or obscure behaviors that can lead to bugs. These behaviors are known as traps, pitfalls, and corner cases. Every platform has them, but Java has far fewer than other platforms of comparable power. The goal of the book is to entertain you with puzzles while teaching you to avoid the underlying traps and pitfalls. By working through the puzzles, you will become less likely to fall prey to these dangers in your code and more likely to spot them in code that you are reviewing or revising. The book is meant to be read with a computer at your side. You’ll need a Java development environment, such as Sun’s JDK. It should support release 5.0, as some puzzles rely on features introduced in this release. Most of the puzzles take the form of a short program that appears to do one thing but actually does something else. That’s why we’ve chosen to decorate the book with optical illusions—drawings that appear to be one thing but are actually another. It’s your job to figure out each the program does. To get the most out of these puzzles, we recommend that you take this approach:

2 A Java Puzzlers Sampler

  1. Study the program and try to predict its behavior without using a computer. If you don’t see a trick, keep looking.
  2. Once you think you know what the program does, run it. Did it do what you thought it would? If not, can you come up with an explanation for the behavior you observed?
  3. Think about how you might fix the program, assuming it is broken.
  4. Then and only then, read the solution.

Unlike most puzzle books, this one alternates between puzzles and their solu- tions. This allows you to read the book without flipping back and forth between puzzles and solutions. The book is laid out so that you must turn the page to get from a puzzle to its solution, so you needn’t fear reading a solution accidentally while you’re still trying to solve a puzzle. We encourage you to read each solution, even if you succeed in solving the puzzle. The solutions contain analysis that goes well beyond a simple explanation of the program’s behavior. They discuss the relevant traps and pitfalls, and provide lessons on how to avoid falling prey to these hazards. Like most best-practice guidelines, these lessons are not hard-and-fast rules, but you should violate them only rarely and with good reason. Most solutions contain references to relevant sections of The JavaLanguage Specification, Third Edition [JLS]. These references aren’t essential to under- standing the puzzles, but they are useful if you want to delve deeper into the lan- guage rules underlying the puzzles. Similarly, many solutions contain references to relevant items in Effective JavaProgramming Language Guide [EJ]. These references are useful if you want to delve deeper into best practices. Some solutions contain discussions of the language or API design decisions that led to the danger illustrated by the puzzle. These “lessons for language designers” are meant only as food for thought and, like other food, should be taken with a grain of salt. Language design decisions cannot be made in isolation. Every language embodies thousands of design decisions that interact in subtle ways. A design decision that is right for one language may be wrong for another. The book contains two appendices. Appendix A is a catalog of the traps and pitfalls in the Java platform. It provides a concise taxonomy of the anomalies exploited by the puzzles, with references back to the puzzles and to other relevant resources. Appendix B is describes the optical illusions contained in the book. This sampler includes the relevant parts of Appendix B.

4 A Java Puzzlers Sampler

Solution 1: The Joy of Hex

It seems obvious that the program should print 1cafebabe. After all, that is the sum of the hex numbers 100000000 16 and cafebabe 16. The program uses long arithmetic, which permits 16 hex digits, so arithmetic overflow is not an issue. Yet, if you ran the program, you found that it prints cafebabe, with no leading 1 digit. This output represents the low-order 32 bits of the correct sum, but some- how the thirty-third bit gets lost. It is as if the program were doing int arithmetic instead of long, or forgetting to add the first operand. What’s going on here? Decimal literals have a nice property that is not shared by hexadecimal or octal literals: Decimal literals are all positive [JLS 3.10.1]. To write a negative decimal constant, you use the unary negation operator (-) in combination with a decimal literal. In this way, you can write any int or long value, whether positive or negative, in decimal form, and negative decimal constants are clearly identi- fiable by the presence of a minus sign. Not so for hexadecimal and octal literals. They can take on both positive and negative values. Hex and octal literals are negative if their high-order bit is set. In this program, the number 0xcafebabe is an int constant with its high-order bit set, so it is negative. It is equivalent to the decimal value -889275714. The addition performed by the program is a mixed-type computation : The left operand is of type long, and the right operand is of type int. To perform the com- putation, Java promotes the int value to a long with a widening primitive conver- sion [JLS 5.1.2] and adds the two long values. Because int is a signed integral type, the conversion performs sign extension : It promotes the negative int value to a numerically equal long value. The right operand of the addition, 0xcafebabe, is promoted to the long value 0xffffffffcafebabeL. This value is then added to the left operand, which is 0x100000000L. When viewed as an int, the high-order 32 bits of the sign- extended right operand are -1, and the high-order 32 bits of the left operand are 1. Add these two values together and you get 0 , which explains the absence of the leading 1 digit in the program’s output. Here is how the addition looks when done in longhand. (The digits at the top of the addition are carries.)

11 11 11 1 0xffffffffcafebabeL

  • 0x0000000100000000L 0x00000000cafebabeL

Puzzle 2: Line Printer 5

Fixing the problem is as simple as using a long hex literal to represent the right operand. This avoids the damaging sign extension, and the program prints the expected result of 1cafebabe:

public class JoyOfHex { public static void main(String[] args) { System.out.println( Long.toHexString(0x100000000L + 0xcafebabe L )); } }

The lesson of this puzzle is that mixed-type computations can be confusing, more so given that hex and octal literals can take on negative values without an explicit minus sign. To avoid this sort of difficulty, it is generally best to avoid mixed-type computations. For language designers, it is worth considering sup- port for unsigned integral types, which eliminate the possibility of sign extension. One might argue that negative hex and octal literals should be prohibited, but this would likely frustrate programmers, who often use hex literals to represent values whose sign is of no significance.

Puzzle 2: Line Printer

The line separator is the name given to the character or characters used to separate lines of text, and varies from platform to platform. On Windows, it is the CR char- acter (carriage return) followed by the LF character (linefeed). On UNIX, it is the LF character alone, often referred to as the newline character. The following pro- gram passes this character to println. What does it print? Is its behavior platform dependent?

public class LinePrinter { public static void main(String[] args) { // Note: \u000A is Unicode representation of linefeed (LF) char c = 0x000A; System.out.println(c); } }

Puzzle 3: A Big Delight in Every Byte 7

The easiest way to fix the program is to remove the Unicode escape from the comment, but a better way is to initialize c with an escape sequence instead of a hex integer literal, obviating the need for the comment:

public class LinePrinter { public static void main(String[] args) { char c = ’\n’; System.out.println(c); } }

Once this has been done, the program will compile and run, but it’s still a questionable program. It is platform dependent for exactly the reason suggested in the puzzle. On certain platforms, such as UNIX, it will print two complete line separators; on others, such as Windows, it won’t. Although the output may look the same to the naked eye, it could easily cause problems if it were saved in a file or piped to another program for subsequent processing. If you want to print two blank lines, you should invoke println twice. As of release 5.0, you can use printf instead of println, with the format string "%n%n". Each occurrence of the characters %n will cause printf to print the appropriate platform-specific line separator. Hopefully, this puzzle convinced you that Unicode escapes can be thoroughly confusing. The lesson is simple: Avoid Unicode escapes except where they are truly necessary. They are rarely necessary.

Puzzle 3: A Big Delight in Every Byte

This program loops through the byte values, looking for a certain value. What does the program print?

public class BigDelight { public static void main(String[] args) { for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) { if (b == 0x90) System.out.print("Joy!"); } } }

8 A Java Puzzlers Sampler

Solution 3: A Big Delight in Every Byte

The loop iterates over all the byte values except Byte.MAX_VALUE, looking for Ox90. This value fits in a byte and is not equal to Byte.MAX_VALUE, so you might think that the loop would hit it once and print Joy! on that iteration. Looks can be deceiving. If you ran the program, you found that it prints nothing. What happened? Simply put, Ox90 is an int constant that is outside the range of byte values. This is counterintuitive because Ox90 is a two-digit hexadecimal literal. Each hex digit takes up 4 bits, so the entire value takes up 8 bits, or 1 byte. The problem is that byte is a signed type. The constant 0x90 is a positive int value of 8 bits with the highest bit set. Legal byte values range from −128 to +127, but the int con- stant 0x90 is equal to +144. The comparison of a byte to an int is a mixed-type comparison. If you think of byte values as apples and int values as oranges, the program is comparing apples to oranges. Consider the expression ((byte)0x90 == 0x90). Appearances notwithstanding, it evaluates to false. To compare the byte value (byte)0x90 to the int value 0x90, Java promotes the byte to an int with a widening primitive conversion [JLS 5.1.2] and compares the two int values. Because byte is a signed type, the conversion performs sign extension, promoting negative byte values to numerically equal int values. In this case, the conversion promotes (byte)0x to the int value -112, which is unequal to the int value 0x90, or +144. Mixed-type comparisons are always confusing because the system is forced to promote one operand to match the type of the other. The conversion is invisible and may not yield the results that you expect. There are several ways to avoid mixed-type comparisons. To pursue our fruit metaphor, you can choose to com- pare apples to apples or oranges to oranges. You can cast the int to a byte, after which you will be comparing one byte value to another:

if (b == (byte)0x90) System.out.println("Joy!");

Alternatively, you can convert the byte to an int, suppressing sign extension with a mask, after which you will be comparing one int value to another:

if ((b & 0xff) == 0x90) System.out.println("Joy!");

Either of these solutions works, but the best way to avoid this kind of problem is to move the constant value outside the loop and into a constant declaration.

10 A Java Puzzlers Sampler

Solution 4: Hello, Goodbye

The program contains two println statements: one in a try block and the other in the corresponding finally block. The try block executes its println and fin- ishes execution prematurely by calling System.exit. At this point, you might expect control to transfer to the finally block. If you tried the program, though, you found that it never can say goodbye: It prints only Hello world. It is true that a finally block is executed when a try block completes execu- tion whether normally or abruptly. In this program, however, the try block does not complete execution at all. The System.exit method halts the execution of the current thread and all others dead in their tracks. The presence of a finally clause does not give a thread special permission to continue executing. When System.exit is called, the virtual machine performs two cleanup tasks before shutting down. First, it executes all shutdown hooks that have been regis- tered with Runtime.addShutdownHook. This is useful to release resources exter- nal to the VM. Use shutdown hooks for behavior that must occur before the VM exits. The following version of the program demonstrates this technique, printing both Hello world and Goodbye world, as expected:

public class HelloGoodbye { public static void main(String[] args) { System.out.println("Hello world"); Runtime.getRuntime().addShutdownHook( new Thread() { public void run() { System.out.println("Goodbye world"); } }); System.exit(0); } }

The second cleanup task performed by the VM when System.exit is called concerns finalizers. If either System.runFinalizersOnExit or its evil twin Runtime.runFinalizersOnExit has been called, the VM runs the finalizers on all objects that have not yet been finalized. These methods were deprecated a long time ago and with good reason. Never call System.runFinalizersOnExit or Runtime.runFinalizersOnExit for any reason: They are among the most dangerous methods in the Java libraries. Calling these methods can result in

Puzzle 5: Larger Than Life 11

finalizers being run on live objects while other threads are concurrently manipu- lating them, resulting in erratic behavior or deadlock. In summary, System.exit stops all program threads immediately; it does not cause finally blocks to execute, but it does run shutdown hooks before halting the VM. Use shutdown hooks to terminate external resources when the VM shuts down. It is possible to halt the VM without executing shutdown hooks by calling System.halt, but this method is rarely used.

Puzzle 5: Larger Than Life

Lest you think that this book is going entirely to the dogs, this puzzle concerns roy- alty. If the tabloids are to be believed, the King of Rock ’n’ Roll is still alive. Not one of his many impersonators but the one true Elvis. This program estimates his current belt size by projecting the trend observed during his public performances. The pro- gram uses the idiom Calendar.getInstance().get(Calendar.YEAR), which returns the current calendar year. What does the program print?

public class Elvis { 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 a size " + INSTANCE.beltSize() + " belt."); } }

Puzzle 6: What’s in a Name? 13

Some common design patterns are naturally subject to initialization cycles, notably the Singleton [Gamma95], which is illustrated in this puzzle, and the Ser- vice Provider Framework [EJ Item 1]. The Typesafe Enum pattern [EJ Item 21] also causes class initialization cycles. Release 5.0 adds linguistic support for this pattern with enum types. To reduce the likelihood of problems, there are some restrictions on static initializers in enum types [JLS 16.5, 8.9]. In summary, be careful of class initialization cycles. The simplest ones involve only a single class, but they can also involve multiple classes. It isn’t always wrong to have class initialization cycles, but they may result in constructor invocation before static fields are initialized. Static fields, even final static fields, may be observed with their default value before they are initialized.

Puzzle 6: What’s in a Name?

This program consists of a simple immutable class that represents a name, with a main method that puts a name into a set and checks whether the set contains the name. What does the program print?

import java.util.*;

public class Name { private final String first, last;

public Name(String first, String last) { this.first = first; this.last = last; }

public boolean equals(Object o) { if (!(o instanceof Name)) return false; Name n = (Name)o; return n.first.equals(first) && n.last.equals(last); }

public static void main(String[] args) { Set s = new HashSet(); s.add(new Name("Mickey", "Mouse")); System.out.println( s.contains(new Name("Mickey", "Mouse"))); } }

14 A Java Puzzlers Sampler

Solution 6: What’s in a Name?

A Name instance consists of a first name and a last name. Two Name instances are equal, as computed by the equals method, if their first names are equal and their last names are equal. First names and last names are compared using the equals method defined in String. Two strings are equal if they consist of the same char- acters in the same order. Therefore, two Name instances are equal if they represent the same name. For example, the following method invocation returns true:

new Name("Mickey", "Mouse").equals(new Name("Mickey", "Mouse"))

The main method of the program creates two Name instances, both represent- ing Mickey Mouse. The program puts the first instance into a hash set and then checks whether the set contains the second. The two Name instances are equal, so it might seem that the program should print true. If you ran it, it almost certainly printed false. What is wrong with the program? The bug is that Name violates the hashCode contract. This might seem strange, as Name doesn’t even have a hashCode method, but that is precisely the problem. The Name class overrides the equals method, and the hashCode contract demands that equal objects have equal hash codes. To fulfill this contract, you must over- ride hashCode whenever you override equals [EJ Item 8]. Because it fails to override hashCode, the Name class inherits its hashCode implementation from Object. This implementation returns an identity-based hash code. In other words, distinct objects are likely to have unequal hash values, even if they are equal. Name does not fulfill the hashCode contract, so the behavior of a hash set containing Name elements is unspecified. When the program puts the first Name instance into the hash set, the set puts an entry for this instance into a hash bucket. The set chooses the hash bucket based on the hash value of the instance, as computed by its hashCode method. When it checks whether the second Name instance is contained in the hash set, the program chooses which bucket to search based on the hash value of the second instance. Because the second instance is distinct from the first, it is likely to have a different hash value. If the two hash values map to different buckets, the contains method will return false: The beloved rodent is in the hash set, but the set can’t find him. Suppose that the two Name instances map to the same bucket. Then what? All HashSet implementations that we know of have an optimization in which each entry stores the hash value of its element in addition to the element itself. When

16 A Java Puzzlers Sampler

Puzzle 7: All Strung Out

One name can be used to refer to multiple classes in different packages. This pro- gram explores what happens when you reuse a platform class name. What do you think it does? Although this is the kind of program you’d normally be embarrassed to be seen with, go ahead and lock the doors, close the shades, and give it a try:

public class StrungOut { public static void main(String[] args) { String s = new String("Hello world"); System.out.println(s); } }

class String { private final java.lang.String s;

public String(java.lang.String s) { this.s = s; }

public java.lang.String toString() { return s; } }

Puzzle 8: Reflection Infection 19

Broadly speaking, the lesson of this puzzle is to avoid the reuse of class names, especially Java platform class names. Never reuse class names from the package java.lang. The same lesson applies to library designers. The Java plat- form designers slipped up a few times. Notable examples include java.sql.Date, which conflicts with java.util.Date, and org.omg.CORBA.Object. This lesson is a specific case of the principle that you should avoid name reuse, with the exception of overriding. For platform implementers, the lesson is that diagnostics should make clear the reason for a failure. The VM could easily have distin- guished the case where there is no main method with the correct signature from the case where there is no main method at all.

Puzzle 8: Reflection Infection

This puzzle illustrates a simple application of reflection. What does this program print?

import java.util.; import java.lang.reflect.;

public class Reflector { public static void main(String[] args) throws Exception { Set s = new HashSet(); s.add("foo"); Iterator it = s.iterator(); Method m = it.getClass().getMethod("hasNext"); System.out.println(m.invoke(it)); } }

20 A Java Puzzlers Sampler

Solution 8: Reflection Infection

The program creates a set with a single element in it, gets an iterator over the set, invokes the iterator’s hasNext method reflectively, and prints the result of the method invocation. As the iterator hasn’t yet returned the set’s sole element, hasNext should return true. Running the program, however, tells a different story:

Exception in thread "main" IllegalAccessException: Class Reflector can not access a member of class HashMap $HashIterator with modifiers "public" at Reflection.ensureMemberAccess(Reflection.java:65) at Method.invoke(Method.java:578) at Reflector.main(Reflector.java:11)