谜题56:大问题
作为一项热身活动,我们来测试一下你对BigInteger的了解程度。下面这个程序将打印出什么呢? import java.math.BigInteger; public class BigProblem { public static void main(String[ ] args) { BigInteger fiveThousand = new BigInteger("5000"); BigInteger fiftyThousand = new BigInteger("50000"); BigInteger fiveHundredThousand = new BigInteger("500000"); BigInteger total = BigInteger.ZERO; total.add(fiveThousand); total.add(fiftyThousand); total.add(fiveHundredThousand); System.out.println(total); } }题目:输出0
谜题57:名字里有什么?
下面的程序包含了一个简单的不可变类,它表示一个名字,其main方法将一个名字置于一个集合中,并检查该集合是否确实包含了该名字。那么,这个程序到底会打印出什么呢? import java.util.*; public class Name { private 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"))); } }题目: 输出 false;
原因;hashCode约定要求相等的对象要具有相同的散列码。为了遵守这项约定,无论何时,只要你覆写了equals方法,你就必须同时覆写hashCode方法[EJ Item 8]。
解决:
public int hashCode() { return 37 * first.hashCode() + last.hashCode(); } 总之,当你覆写equals方法时,一定要记着覆写hashCode方法。更一般地讲,当你在覆写一个方法时,如果它具有一个通用的约定,那么你一定要遵守它。对于大多数在Object中声明的非final的方法,都需要注意这一点[EJ Chapter 3]。不采用这项建议就会导致任意的、不确定的行为。
谜题58:产生它的散列码
本谜题试图从前一个谜题中吸取教训。下面的程序还是由一个Name类和一个main方法构成,这个main方法还是将一个名字放置到一个散列集合中,然后检查该集合是否包含了这个名字。然而,这一次Name类已经覆写了hashCode方法。那么下面的程序将打印出什么呢? import java.util.*; public class Name { private String first, last; public Name(String first, String last) { this.first = first; this.last = last; } public boolean equals(Name n) { return n.first.equals(first) && n.last.equals(last); } public int hashCode() { return 31 * first.hashCode() + last.hashCode(); } public static void main(String[ ] args) { Set s = new HashSet(); s.add(new Name("Donald", "Duck")); System.out.println( s.contains(new Name("Donald", "Duck"))); } }题目:输出 false;
谜题59:什么是差?
下面的程序在计算一个int数组中的元素两两之间的差,将这些差置于一个集合中,然后打印该集合的尺寸大小。那么,这个程序将打印出什么呢? import java.util.*; public class Differences { public static void main(String[ ] args) { int vals[ ] = { 789, 678, 567, 456, 345, 234, 123, 012 }; Set diffs = new HashSet(); for (int i = 0; i < vals.length; i++) for (int j = i; j < vals.length; j++) diffs.add(vals[i] - vals[j]); System.out.println(diffs.size()); } }题目:千万不要在一个整型字面常量的前面加上一个0;这会使它变成一个八进制字面常量。有意识地使用八进制整型字面常量的情况相当少见,你应该对所有的这种特殊用法增加注释。
谜题60:一行的方法
略!
谜题61:日期游戏
下面的程序演练了Date和Calendar类的某些基本特性,它会打印出什么呢? import java.util.*; public class DatingGame { public static void main(String[ ] args) { Calendar cal = Calendar.getInstance(); cal.set(1999, 12, 31); // Year, Month, Day System.out.print(cal.get(Calendar.YEAR) + " "); Date d = cal.getTime(); System.out.println(d.getDay()); } }题目:它应该表示的是1999年的除夕夜,然后该程序打印年份和日。看起来该程序应该打印1999 31,但是它没有;它打印的是2000 1。
原因:
Date将一月表示为0,而Calendar延续了这个错误。因此,这个方法调用将日历设置到了1999年第13个月的第31天。但是标准的(西历)日历只有12个月,该方法调用肯定应该抛出一个IllegalArgumentException异常,对吗?它是应该这么做,但是它并没有这么做。Calendar类直接将其替换为下一年,在本例中即2000年的第一个月。这也就解释了我们的程序为什么打印出的第一个数字是2000。
解决方案:它叙述道Date.getDay返回的是Date实例所表示的星期日期,而不是月份日期。这个返回值是基于0的,从星期天开始计算。因此程序所打印的1表示2000年1月31日是星期一。请注意,相应的Calendar方法get(Calendar.DAY_OF_WEEK) 不知为什么返回的是基于1的星期日期值,而不是像Date的对应方法那样返回基于0的星期日期值。有两种方法可以订正这个问题。你可以调用Date.date这一名字极易让人混淆的方法,它返回的是月份日期。然而,与大多数Date方法一样,它已经被弃用了,因此你最好是将Date彻底抛弃,直接调用Calendar的get(Calendar.DAY_OF_MONTH)方法。用这两种方法,该程序都可以打印出我们想要的1999 31:
谜题62:名字游戏
下面的程序将两个映射关系放置到了一个映射表中,然后打印它们的尺寸。那么,它会打印出什么呢? import java.util.*; public class NameGame { public static void main(String args[ ]) { Map<String, String> m = new IdentityHashMap<String, String>(); m.put("Mickey", "Mouse"); m.put("Mickey", "Mantle"); System.out.println(m.size()); } }题目: 输出1
谜题63:更多同样的问题
下面的程序除了是面向对象的这一点之外,与前一个非常相似。因为从前一个程序中已经吸取了教训,这个程序使用了一个通用目的的Map实现,即一个HashMap,来替代前一个程序的IdentityHashMap。那么,这个程序会打印出什么呢? import java.util.*; public class MoreNames { private Map<String,String> m = new HashMap<String,String>(); public void MoreNames() { m.put("Mickey", "Mouse"); m.put("Mickey", "Mantle"); } public int size() { return m.size(); } public static void main(String args[ ]) { MoreNames moreNames = new MoreNames(); System.out.println(moreNames.size()); } }题目: 问题在于MoreNames没有任何程序员声明的构造器。它拥有的只是一个返回值为void的实例方法,即MoreNames,作者可能是想让它作为构造器的。遗憾的是,返回类型(void)的出现将想要的构造器声明变成了一个方法声明,而且该方法永远都不会被调用。因为MoreNames没有任何程序员声明的构造器,所以编译器会帮助(真的是在帮忙吗?)生成一个公共的无参数构造器,它除了初始化它所创建的域实例之外,不做任何事情。就像前面提到的,m被初始化成了一个空的HashMap。当在这个HashMap上调用size方法时,它将返回0
谜题64:按余数编组
下面的程序将生成整数对3取余的柱状图,那么,它将打印出什么呢? public class Mod { public static void main(String[ ] args) { final int MODULUS = 3; int[] histogram = new int[MODULUS]; // Iterate over all ints (Idiom from Puzzle 26) int i = Integer.MIN_VALUE; do { histogram[Math.abs(i) % MODULUS]++; } while (i++ != Integer.MAX_VALUE); for (int j = 0; j < MODULUS; j++) System.out.println(histogram[j] + " "); } }题目: java.lang.ArrayIndexOutOfBoundsException
原因:Integer.MIN_VALUE == Math.abs(Integer.MIN_VALUE)
谜题65:一种疑似排序的惊人传奇
下面的程序使用定制的比较器,对一个由随机挑选的Integer实例组成的数组进行排序,然后打印了一个描述了数组顺序的单词。回忆一下,Comparator接口只有一个方法,即compare,它在第一个参数小于第二个参数时返回一个负数,在两个参数相等时返回0,在第一个参数大于第二个参数时返回一个整数。这个程序是展示5.0版特性的一个样例程序。它使用了自动包装和解包、泛型和枚举类型。那么,它会打印出什么呢? import java.util.*; public class SuspiciousSort { public static void main(String[ ] args) { Random rnd = new Random(); Integer[ ] arr = new Integer[100]; for (int i = 0; i < arr.length; i++) arr[i] = rnd.nextInt(); Comparator<Integer> cmp = new Comparator<Integer>() { public int compare(Integer i1, Integer i2) { return i2 - i1; } }; Arrays.sort(arr, cmp); System.out.println(order(arr)); } enum Order { ASCENDING, DESCENDING, CONSTANT, UNORDERED }; static Order order(Integer[ ] a) { boolean ascending = false; boolean descending = false; for (int i = 1; i < a.length; i++) { ascending |= a[i] > a[i-1]; descending |= a[i] < a[i-1]; } if (ascending && !descending) return Order.ASCENDING; if (descending && !ascending) return Order.DESCENDING; if (!ascending) return Order.CONSTANT; // All elements equal return Order.UNORDERED; // Array is not sorted } }题目: unorder
原因: 两个一正一负数相减会溢出,导致比较器不正常工作!