24 Jun 2014

10个Java开发人员易犯的错误(译)

下面总结了10个Java程序员最易犯的错误.

1.Array与ArrayList转换

将一个数组array转换为ArrayList,程序猿通常这样做:

List<String> list = Arrays.asList(arr);

Arrays.asList() 会返回一个Arrays的内部私有静态类的对象.该内部类实现了List接口,而不是java.util.ArrayList.java.util.Arrays.ArrayList类包含方法:set(), get(), contains(),但是不包含添加元素的方法,因此该list对象的定长的.

如果要创建一个真正的ArrayList,你需要以下操作:

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));

ArrayList的构造函数可以接收Collection类型,Collection同样是java.util.Arrays.ArrayList的基类.

2. 检测Array中是否包含某值

可以通过以下方式实现:

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);

以上代码是可用的, 但是没有必要先将List转化为Set,这样会带来额外的时间开销. 因此,可以简化为以下方式:

Arrays.asList(arr).contains(targetValue);

或者

for(String s: arr){
  if(s.equals(targetValue))
    return true;
}
return false;

相较于第二段代码,第一段有更好的可读性.

3.通过循环在List中移除元素

以下代码为在循环迭代中移除元素:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
  list.remove(i);
}
System.out.println(list);

输出为: [b, d]

这个方法中有一个严重的问题.当元素被移除后,list的大小和索引会发生变化. 因此,如果想通过遍历索引来删除多个集合中的元素,以上代码不会起到相应的作用.

你或许已经了解可以通过迭代器来删除元素,并且你也知道增强型for循环的工作原理就是迭代器.

但是实际上这是错误的.参考以下代码:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));

for (String s : list) {
  if (s.equals("a"))
    list.remove(s);
}

这段代码将会抛出ConcurrentModificationException.

替换为以下代码,则功能可用:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
  String s = iter.next();

  if (s.equals("a")) {
    iter.remove();
  }
}

.next()的调用必须发生在.remove()之前. 在增强for循环中,编译器将会使.next()的调用发生在remove操作之后, 这将会引发ConcurrentModificationException. 如果有兴趣,可用参考ArrayList.iterator()的源码.

4.Hashtable vs HashMap

依照算法中的约定,Hashtable是一种数据结构的名称.但是在Java中,这种数据结果的名称是HashMap.Hashtable和HashMap一个最重要的区别在于Hashtable是线程安全的.因此,通常你不会用到Hashtable,取而代之的是应该应用HashMap.

5. Collection中的原始类型

Java中, 原生类型与无界通配符类型很容易混入到一起. 以Set为例, Set是原生类型, 而Set<?>则是无界通配符类型

参考以下代码,它将原生类型作为方法参数:

public static void add(List list, Object o){
  list.add(o);
}
public static void main(String[] args){
  List<String> list = new ArrayList<String>();
  add(list, 10);
  String s = list.get(0);
}

这段代码将抛出以下异常:

Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at … 使用原生类型是很危险的,因为原生类型的集合会跳过泛型检查且不安全.因此,Set, Set<?> 和 Set有很大的区别.

6.访问级别

程序猿们通常会使用public来修饰类属性. 这样便于通过直接引用来获取属性值, 但这却是一个很糟糕的设计. 最佳实践应为:尽可能使用低级别的访问权限.

public, default, protected, 和private

7.ArrayList vs. LinkedList

当程序猿们不了解ArrayList和LinkedList的区别时候, 他们通常会使用ArrayList, 应为它看起来更加熟悉. 然后, 他们之间会有巨大的性能差别. 大致上, 在有大量增/删操作,并且没有频繁随机取值的前提下, LinkedList是一个更优的选择.

8. Mutable vs. Immutable

不可变对象有很多的有优点,诸如:简单, 安全等等. 但是对于每一个不同的值, 都需要一个单独的对象, 并且大量的对象会导致垃圾收集的巨大开销. 因此在可变与不可变对象的选择上,应该寻求一个平衡.

通常,可变对象被用于避免产生大量的中间对象. 一个经典的例子是利用可变对象串联大量的字符串. 如果你用不可变对象, 将会产生大量的,会立即被垃圾回收的对象. 这种情况造成大量的耗时与CPU的开销, 因此可变对象是正确的解决方案(例如:StringBuilder).

String result="";
for(String s: arr){
  result = result + s;
}

还存在一些其他的场景会应用到可变对象. 例如:将可变对象传进方法中, 可以收集多个结果,而不需要经过大量的syntactic hoops. 另一个例子是排序和过滤: 当然, 你可以创建一个方法来保存原始集合,之后返回一个排序后的集合, 但是对于更大的集合来说,这种做法的成本很高(From dasblinkenlight’s answer on Stack Overflow).

9. Constructor of Super and Sub

由于没有定义父类的默认构造方法而引起的编译错误是很常见的. 在Java中, 如果一个类中没有定义构造方法, 编译器会默认天假一个无参的构造方法. 如果父类中定一个的构造器, 例如: Super(String s), 编译器则不会自动添加无参构造方法. 这就是上述父类的场景.

子类的构造方法, 有参与无参的方法都会调用父类无参构造方法. 由于编译器会尝试在子类的2个构造方法内添加super(), 但是父类中没有定义默认的构造方法, 编译器会产生编译错误. .. 为了修复此问题:

1) 在父类中添加Super() 构造方法:

public Super(){
    System.out.println("Super");
}

2) 移除父类中自定义的构造方法remove. 3) 子类构造函数中调用super(value).

10. “” or Constructor?

字符串可以通过两种方式进行创建:

//1. use double quotes
String x = "abc";
//2. use constructor
String y = new String("abc");

以上两者有何区别?

以下例子给出一个直接的答案:

String a = "abcd";
String b = "abcd";
System.out.println(a == b);  // True
System.out.println(a.equals(b)); // True

String c = new String("abcd");
String d = new String("abcd");
System.out.println(c == d);  // False
System.out.println(c.equals(d)); // True

后记

以上的列表,是基于我对于GitHub中大量开源项目,StackOverflow中提问以及谷歌中热门搜索的分析. 并没有依据来证明它们是就是top 10, 但是的确是非常普遍的问题.如果有任何疑问, 请在下方进行评论.非常感谢您来指出的一些更加常见的错误,


Tags:
0 comments