吐槽,Java 设计的槽点
今天不灌水,直接上干货!希望下面的讲解,能与你产生一些共鸣。
1. 求长度各有千秋
你是否曾经在面试的时候,经常被问到:数组有没有 length() 方法?字符串有没有 length() 方法? 集合有没有 length() 方法?
面对这个问题,那么不得不吐槽一下,Java 中获取长度的方式,设计着实有点乱,对刚入门的程序猿而言,那绝对是一脸的懵逼。
String[] array = {"abc", "def"}; String str = "abcedf"; List<String> list = new ArrayList<String>(); list.add("abc"); list.add("def"); System.out.println("数组的长度: " + array.length); System.out.println("字符串的长度: " + str.length()); System.out.println("集合的长度: " + list.size());
正式科普一下,希望能够铭记你心中。数组求长度用 length 属性;字符串求长度用 length() 方法;集合求长度用 size() 方法。
2. 字符串截取有深意
对于程序猿来说,编程规范能够养成良好的编程习惯,提高代码质量,减少沟通成本。阿里 Java 开发手册编程规约中记载,【强制】方法名、參数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
看到这里,不得不提 String 中的 substring 方法,你是不是经常把“substring”写成“subString”。本次这个命名不是吐槽的重点。主要想分享如下代码片段。
public class StringInterview { public static void main(String[] args) { String str = "......abcdefgh......."; String subStr = str.substring(1,3); str = null; System.out.println(subStr); } }
假如上述这段程序在 Java 1.6 中运行,代码中虽然强制使 str 引用为空,本意是释放 str 占用的空间,但是这个时候,GC 是无法回收这个大的 char 数组的,因为还在被 subStr 字符串内部引用着,虽然 subStr 只截取这个大数组的一小部分。当 str 是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来内存泄露问题。
深入 Java 1.6 中 substring 的设计一探究竟。
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { 。。 。。 。。 } if (endIndex > count) { 。。 。。 。。 } if (beginIndex > endIndex) { 。。 。。 。。 } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); }
上述方法调用的构造方法
String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
到此你应该拨云见日豁然开朗。当我们调用字符串 str 的 substring 得到字符串 subStr,其实这个操作,无非就是调整了一下 subStr 的 offset 和 count ,用到的内容还是 str 之前的 value 字符数组,并没有重新创建新的专属于 subStr 的内容字符数组。如果 subStr 的生命周期要长于 str 或者手动设置 str 为null,当垃圾回收进行后,str 被回收掉,subStr 没有回收掉,那么内存占用依旧存在,因为 subStr 持有 str 字符数组的引用。
正式科普一下,这个问题出现在 Java 1.6,并且 Java 1.7 中已经修复。虽然已经修复,并不代表我们就不需要了解,如果你正在求职路上,稍微了解一下,说不定会加分。
3. 一条 if 语句引发不满
先给各位抛一段 Java LinkedList 类的代码片段,一起吐槽吐槽。
public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; }
JDK 中 if 语句后只有一条语句,大部分都是这么实现的。原则上,if 语句如果后面跟着只有一句话,是可以不加的。但是在我们实际开发中,有些现象却会让你匪夷所思,不信你看看下面的代码片段。
片段一:
if (f == null) //抛出异常,或者加一条打印语句,加上此句注释逻辑就变了 throw new NoSuchElementException();
片段二:
public class Interview { public static void main(String[] args) { Class c = Interview.class; try { Object o = c.newInstance(); if (o instanceof Interview) Interview tt = (Interview) o; //为什么会报错?请各位解释原因 } catch (Exception e) { e.printStackTrace(); } } }
正式科普一下,看似一个简单的编码规范,背后隐藏了多少坑啊,所以为了良好的编程习惯,建议还是统一加上大括号为好,良好的编码习惯是真重要啊。
4. 时间实现也找茬
Tiago Fernandez 做过一次投票,选举最烂的 Java API,排第二的就是日期 API(Date 和Calender)。一言不合就抛代码,如下片段是计算两个日期之间的天数。
public static void main(String[] args) { Calendar begin = Calendar.getInstance(); begin.set(1990, Calendar.JUNE, 17); Calendar end = Calendar.getInstance(); System.out.println(alculatedDays(begin, end)); System.out.println(alculatedDays(begin, end)); // 为什么显示 0? } public static long alculatedDays(Calendar begin, Calendar end) { long days = 0; while (begin.before(end)) { begin.add(Calendar.DAY_OF_MONTH, 1); days++; } return days; }
alculatedDays 方法,如果连续计算两个 Date 实例的话,第二次会取得 0,因为 Calendar 状态是可变的,考虑到重复计算的场合,最好复制一个新的 Calendar,改造如下
public static long alculatedDays(Calendar begin, Calendar end) { Calendar calendar = (Calendar) begin.clone(); // 复制 long days = 0; while (calendar.before(end)) { calendar.add(Calendar.DAY_OF_MONTH, 1); days++; } return days; }
不过万物都在向前进化,因为由于原来老旧的日期 API 一直被人诟病,所以 JDK 1.8 中对日期的改动是特别大的,基本上是引入了一套全新易用的 API,各位有时间可以体验一下。
好了,吐槽中见真谛,今天就讲这么多吧。希望你能 get 到一点点共鸣,如果你比较感兴趣,就多多分享给身边的朋友吧。