目录
7. Collectios.toMap dumplicateKey
1. NPE的判断
引用别人的一句话:“任何你怀疑会发生NPE的地方,总有一天会发生”,判空永远不是多余步骤,同时返回值也尽量不要返回null。
2. Timsort报错
java.util.Arrays#sort(T[], int, int, java.util.Comparator<? super T>)方法:
public static <T> void sort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> c)
{
if (c == null)
{
sort(a, fromIndex, toIndex);
}
else
{
rangeCheck(a.length, fromIndex, toIndex);
// 此方法是1.7前的实现,1.7及后版本默认不开启
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex, c);
else
TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
}
}
可以看到,使用sort方法默认是使用的TimSort的算法实现,其中比较器需要满足以下三个条件,否则将会抛出异常IllegalArgumentException("Comparison method violates its general contract!"):
1 ) 自反性: x , y 的比较结果和 y , x 的比较结果相反。
2 ) 传递性: x > y , y > z ,则 x > z 。
3 ) 对称性: x = y ,则 x , z 比较结果和 y , z 比较结果相同。
3. 三元运算符的隐式向下转型-NPE
TestObj src = new TestObj();
TestObj dst = new TestObj();
dst.setId(src.getId() != null ? 0L : src.getId());
比如在jdk中则会抛出NPE,在IDE中直接已经给出了提示:
但需要注意的是,以下类似的代码在1.8版本中却可以正常执行,因为根据JLS15.25.3,1.8版本增强了类型推断功能,尽管有泛型擦除机制,但是根据JLS规定,编译器根据合成表达式和当前的返回值类型推断出第二个表达式和第三个表达式的结果都是Boolean,不存在拆箱过程就没有异常产生:
Map<String,Boolean> map = new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("key") : false);
tips:如何查询class文件的编译版本:
可以vim底线模式:%!xxd或者hexdump -C Test.class都可以看到对应的十六进制信息,如果有jdk环境,可以通过javap -v Test.class反编译结果查看版本信息。
java字节码文件魔数0xcafebabe占用四个字节,后面紧接着依次是次版本号和主版本号,只需要记住该版本号是从45开始的,jdk1.1之后每个大版本加一,所以到jdk8应该是52,这个二进制的数字转为十六进制刚好是0x34。
4. 缓存范围外的数值==
System.err.println(Integer.valueOf(-129) == Integer.valueOf(-129)); // false
System.err.println(Integer.valueOf(-128) == Integer.valueOf(-128)); // true
System.err.println(Integer.valueOf(127) == Integer.valueOf(127)); // true
System.err.println(Integer.valueOf(128) == Integer.valueOf(128)); // false
注意Integer的构造函数和valueOf方法的区别即可,Integer的数值缓存范围为[-128,127]。
// 构造函数,直接生成新对象
public Integer(int value) {
this.value = value;
}
// valueOf方法,如果在缓存范围内,则返回缓存对象
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
其中number类型中,short、int、long对应的包装类中都内置了缓存数据。
5. Objects.equals 类型不匹配问题
举一个简单的例子,以下代码运行结果为false。执行过程中1L会自动转型为Long,1自动向上转型为Integer,所以在执行第一个参数的equals方法的时候,因为类型不一致就会返回false,所以使用这个方法的时候,需要特别注意参数的类型是否一致。
System.err.println(Objects.equals(1L, 1)); // false
// Long的equals方法
public boolean equals(Object obj) {
// 类型不一致,直接返回false
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
6. Collections.max参数为空报错
// java.util.NoSuchElementException
List<Long> ids = new ArrayList<>();
Long max = Collections.max(ids);
查看源码可以知道max方法的实现是通过Iterator迭代器实现的,遍历一次所有的元素,选出最大的那个。第一步就是要找到第一个元素作为候选结果和对比对象,其使用Iterator接口的next()方法得到,看next方法的定义可知,如果迭代器为空则直接抛出NoSuchElement的异常,所以迭代器的next的所有具体实现都会涉及到这个逻辑以保持一致,比如java.util.ArrayList.Itr#next()、java.util.LinkedList.ListItr#next()等都有对应的异常抛出逻辑。
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();
// java.util.LinkedList.ListItr#next()
public E next() {
checkForComodification();
// 异常由此处抛出
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
7. Collectios.toMap dumplicateKey
TestObj test1 = new TestObj(1L);
TestObj test2 = new TestObj(1L);
// 忽略返回值
Stream.of(test1, test2).collect(Collectors
.toMap(TestObj::getId, Function.identity()));
报错:java.lang.IllegalStateException: Duplicate key Test$TestObj@506e1b77
因为没有考虑key重复时候的策略,修改为让key重复的元素直接覆盖,报错消失。
TestObj test1 = new TestObj(1L);
TestObj test2 = new TestObj(1L);
Stream.of(test1, test2).collect(Collectors
.toMap(TestObj::getId, Function.identity(), (k1, k2) -> k2));
8. 返回集合的时候尽量将null值过滤掉是个好习惯
Map<Long, TestObj> cache = new HashMap<>();
cache.put(1L, new TestObj(1L));
cache.put(2L, new TestObj(2L));
cache.put(3L, null);
// return ids.stream().map(cache::get).collect(Collectors.toList());
// 建议实现中去掉null值,防止调用者使用时未判断导致的NPE
return ids.stream().map(cache::get).filter(Objects::nonNull).collect(Collectors.toList());