下面总结的十个错误是Java开发者最容易犯的。
#1、赋值Array到ArrayList
传递一个Array到ArrayList,开发者通常这样做:
List<String> list = Arrays.asList(arr);
Arrays.asList()返回的是一个在Arrays内部的一个私有的静态的ArrayList类,它并不是java.util.ArrayList类。ArrayList有set(),get(),contains()方法,但是并没有像添加元素这样的方法,因为它的尺寸是固定的。要创建一个真正的ArrayList,你应该这样做:
ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
ArrayList的构造函数能接收一个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的尺寸缩小和下标增大。因此如果你想要通过下标在循环迭代中删除多个元素将不能行之有效。
你也许知道用iterator在循环内部删除一个元素是一个正确的方式,你也知道Java中的foreach循环像一个iterator一样工作,但是实际并不是这样的。思考一下下面的代码:
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()之前调用。在foreach循环中,编译器将在移除元素操作之后调用next(),这就产生ConcurrentModificationException。你可能想要看一下ArrayList.iterator()的源码吧。
#4、Hashtable vs HashMap
按照算法中习惯,Hashtable是数据结构的名称。但是在Java中,数据结构的名称是HashMap。Hashtable和HashMap之间主要的区别是Hashtable是同步的。因此大多数情况下你不需要Hashtable,而是用HashMap。
#5、用原始类型的集合
Java中,原始类型和通配符类型是很容易混合在一起的。拿Set举个例子,Set是一个原始类型,而Set<?>是一个通配符类型。
考虑一下下面的代码用一个原始类型的List作为一个参数:
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<Object>三者之间有很大的不同。
#6、访问级别
开发者通常把类中的字段设置为Public。通过直接引用可以很容易得到字段值,但是这是一个非常坏的设计。对于成员变量定义一个访问级别的首要准则是尽可能的小。
#7、ArrayList vs. LinkedList
当开发者不知道ArrayList和LinkedList之间的不同时,他们经常用ArrayList,因为它看起来是更熟悉的。然而,在它们之间有一个巨大的性能差异。总结来说,如果有大量的add/remove操作和没有大量的随机访问操作应该用LinkedList。
#8、可变的 vs 不可变的
不可变对象有许多优点,像简单,安全,等等。但是它对于每个不同的值需要一个独立的对象,太多的对象可能造成很大的垃圾回收成本。当在可变和不可变之间选择时应该综合考虑一下。
一般来说,使用可变对象是为了避免产生太多的中间对象。一个经典的例子是链接大量的字符串。如果你用一个不可变的String,将产生许多立即符合垃圾回收的对象。这将消耗CPU的性能和时间,用可变的对象是一个正确的选择(例如:StringBuilder)。
String result="";
for(String s: arr){
result = result + s;
}
有其它的情况可变对象也是合适的。例如通过可变对象进入方法中去收集多个结果而没有用太多的语句块。另一个例子是排序和过滤:当然了,你可以用原始的集合的方法,返回排完序的一个,但是对于一个集合来说十分浪费资源的。
#9、父类和子类的构造函数
存在编译错误是因为默认的父类构造函数没有被定义。在Java中,如果一个类没有定义一个构造函数,编译器将默认为一个类插入一个默认的无参的构造函数。如果在父类中定义一个构造函数,在这个例子中是Super(String s), 编译器将不再插入一个默认的无参的构造函数。就像上面Super类中的情况。
Sub类中的有参或无参构造函数将调用父类中的构造函数。由于编译器试着插入super()到Sub类的两个构造函数中,但是Super的默认构造器没有定义,编译错误。
解决这个问题,是加一个Super()构造函数在Super类中:
public Super(){
System.out.println("Super");
}
或者移除自己定义的Super构造函数,或者加super(value)到子类构造函数中。
#10、""或者Constructor?
String能通过这两种方式被创建:
//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上大量开源工程、Overflow上的问题和受欢迎的Google问卷的分析。没有评估证明它们是准确的前10名,但是它们肯定是非常普遍的问题。如果你不同意一些部分内容,请留下评论。如果你能指出一些更普遍的错误我将十分感激。
原文链接:http://www.programcreek.com/2014/05/top-10-mistakes-java-developers-make/