Java解惑学习有感(六)---库之谜

时间:2023-02-25 22:35:56

谜题  56 :大问题

1、BigInteger 实例是不可变的。String、BigDecimal 以及包装器类型:Integer、Long、Short、Byte、Character、Boolean、Float 和 Double 也是如此,你不能修改它们的值。我们不能修改现有实例的值,对这些类型的操作将返回新的实例。

2、不要被误导,认为不可变类型是可变的。这是一个在刚入门的Java 程序员中很常见的错误。公正地说,Java 不可变类型的某些方法名促使我们走上了歧途。像 add、subtract 和 negate 之类的名字似乎是在暗示这些方法将修改它们所调用的实例。也许 plus、minus 和 negation 才是更好的名字。

谜题  57:名字里有什么

1、当你覆写 equals 方法时,一定要记着覆写 hashCode 方法。更一般地讲,当你在覆写一个方法时,如果它具有一个通用的约定,那么你一定要遵守它。对于大多数在Object中声明的非final的方法,都需要注意这一点

2、如果你覆写equals方法,却没有覆写hashCode方法,那只能从 Object 那里继承了其 hashCode实现,这个实现返回的是基于标识的散列码,换句话说,不同的对象几乎总是产生不相等的散列值,即使它们是相等的也是如此。所以就无法比较了!

谜题  58:产生它的散列码

当你想要进行覆写时,千万不要进行重载。为了避免无意识地重载,你应该机械地对你想要覆写的每一个超类方法都拷贝其声明,或者更好的方式是让你的 IDE 帮你去做这些事。这样做除了可以保护你免受无意识的重载之害,而且还可以保护你免受拼错方法名之害。如果你使用的 5.0 或者更新的版本,那么对于那些意在覆写超类方法的方法,你可以将@Override 注释应用于每一个这样的方法的声明上:@Override public Boolean equals(Object o) { ... }在使用这个注释时,除非被注释的方法确实覆写了一个超类方法,否则它将不能编译。对语言设计者来说,值得去考虑在每一个覆写超类方法的方法声明上都添加一个强制性的修饰符。

谜题  59:什么是差?

千万不要在一个整型字面常量的前面加上一个 0;这会使它变成一个八进制字面常量。有意识地使用八进制整型字面常量的情况相当少见,你应该对所有的这种特殊用法增加注释。对语言设计者来说,在决定应该包含什么特性时,应该考虑到其限制条件。当有所迟疑时,应该将它剔除在外。

谜题  60 :一行的方法

问题1:编写一个方法,它接受一个包含元素的 List,并返回一个新的 List,
它以相同的顺序包含相同的元素,只不过它把第二次以及后续出现的重复
元素都剔除了!

解决思路: LinkedHashSet将集合中的所有重复元素都消除掉并且可以维护其元素被插入的顺序

接着是需要怎样返回一个List,代码如下:

static <E> List<E> withoutDuplicates(List<E> original) {
return new ArrayList<E>(new LinkedHashSet<E>(original));
}

问题2:如果你传递的字符串是”fear, surprise, ruthless efficiency, an almost fanatical
devotion to the Pope, nice red uniforms”,那么你如何得到的将是一个
包含 5 个元素的字符串数组,这些元素是”fear”,”surprise”,”ruthless efficiency”,”an almost fanatical devotion to the Pope” 和 “nice red uniform”?是以","为分割符

解决思路:使用String.split()方法,代码如下:

static String[ ] parse(String string) {
return string.split(",\\S*");
}

问题3:假设你有一个多维数组,出于调试的目的,你想打印它。你不知道这个数组有多少级,以及在数组的每一级中所存储的对象的类型。编写一个方法,它可以向你显示出在每一级上的所有元素。

解决思路:这是一个讲究技巧的问题。你甚至不必去编写一个方法。这个方法在 5.0 或之后的版本中已经提供了,它就是 Arrays.deepToString。如果你传递给它一个对象引用的数组,它将返回一个精密的字符串表示。它可以处理嵌套数组,甚至可以处理循环引用,即一个数组元素直接或间接地引用了其嵌套外层的数组。事实上,5.0 版本中的 Arrays 类提供了一整套的 toString、equals 和 hashCode方法,使你能够打印、比较或散列任何原始类型数组或对象引用数组的内容。

问题4:编写一个方法,它接受两个 int 数值,并在第一个数值与第二个数值以二进制补码形式进行比较,具有更多的位被置位时,返回 true。

解决思路:使用Integer.bitCount,它返回的是一个 int 数值中被置位的位数:代码如下:

static Boolean hasMoreBitsSet(int i, int j) {
return (Integer.bitCount(i) > Integer.bitCount(j));
}

总之,Java 平台的每一个主版本都在其类库中隐藏了一些宝藏。

谜题  61 :日期游戏

致命的 Date/Calendar 问题

问题1:Calendaro类的.set(1999,12,31)中。当月份以数字来表示时,习惯上我们将第一个月被赋值为 1。遗憾的是,Date 将一月表示为 0,而 Calendar 延续了这个错误。因此,这个方法调用将日历设置到了 1999 年第13 个月的第 31 天。但是标准的(西历)日历只有 12 个月,该方法调用肯定应该抛出一个 IllegalArgumentException 异常,对吗?它是应该这么做,但是它并没有这么做。Calendar 类直接将其替换为下一年,在本例中即 2000 年的第一个月。

订正问题:以将 cal.set 调用的第二个参数由 12 改为11,但是这么做容易引起混淆,因为数字 11 会让读者误以为是 11 月。更好的方式是使用 Calendar 专为此目的而定义的常量,即 Calendar.DECEMBER。

问题2: Date.getDay()返回的是 Date实例所表示的星期日期,而不是月份日期。这个返回值是基于 0 的,从星期天开始计算。因此程序所打印的 1 表示 2000 年 1 月 31 日是星期一。

如何得到具体是哪一天呢?可以使用Calendar类的get(Calendar.DAY_OF_MONTH)方法


谜题  62 :名字游戏

1、IdentityHashMap 的文档中叙述道:“这个类用一个散列表实现了 Map 接口,它在比较键时,使用的是引用等价性而不是值等价性”

2、语言规范保证了字符串是内存限定的,换句话说,相等的字符串常量同时也是相同的

3、不要使用 IdentityHashMap,除非你需要其基于标识的语义;它不是一个通用目的的 Map 实现。这些语义对于实现保持拓扑结构的对象图转换(topology-preserving object graph transformations)非常有用,例如序列化和深层复制。

谜题  63 :更多同样的问题

不要因为偶然地添加了一个返回类型,而将一个构造器声明变成了一个方法声明。尽管一个方法的名字与声明它的类的名字相同是合法的,但是你千万不要这么做。更一般地讲,要遵守标准的命名习惯,它强制要求方法名必须以小写字母开头,而类名应该以大写字母开头。

谜题  64 :按余数编组

1、Math.abs 不能保证一定会返回非负的结果。如果它的参数是Integer.MIN_VALUE,或者对于 long 版本的实现传递的是 Long.MIN_VALUE,那么它将返回它的参数。

2、没有任何 int 数值可以表示 Integer.MIN_VALUE 的负值,也没有任何long 数值可以表示 Long.MIN_VALUE 的负值


谜题  65 :一种疑似排序的惊人传奇

1、Arrays.sort 方法已经存在许多年了,它工作得非常好。现在只有一个地方能够发现 bug 了:比较器。乍一看,这个比较器似乎不可能出错。毕竟,它使用的是标准的惯用法:如果你有两个数字,你想得到一个数值,其符号表示它们的顺序,那么你可以计算它们的差。这个惯用法至
iTePub.Net-Collect少从 1970 年代早期就一直存在了,它在早期的 UNIX 里面被广泛地应用。遗憾的是,这种惯用法从来都没有正确地工作过。这种惯用法的问题在于定长的整数没有大到可以保存任意两个同等长度的整数之差的程度。当你在做两个 int 或 long 数值的减法时,其结果可能会溢出,在这种情况下我们就会得到错误的符号。

2、不要使用基于减法的比较器,除非你能够确保要比较的数值之间的差永远不会大于 Integer.MAX_VALUE 。更一般地讲,要意识到 int 的溢出。


如果有疑问或者对该博文有何看法或建议或有问题的,欢迎评论,恳请指正!