10个 Java 开发者常犯的错误

  1. 把数组转成ArrayList

为了将数组转换为ArrayList,开发者经常会这样做:

List list = Arrays.asList(arr);

使用Arrays.asList()方法可以得到一个ArrayList,但是得到这个ArrayList其实是定义在Arrays类中的一个私有的静态内部类。这个类虽然和java.util.ArrayList同名,但是并不是同一个类。java.util.Arrays.ArrayList类中实现了set(), get(), contains()等方法,但是并没有定义向其中增加元素的方法。也就是说通过Arrays.asList()得到的ArrayList的大小是固定的。

如果在开发过程中,想得到一个真正的ArrayList对象(java.util.ArrayList的实例),可以通过以下方式:

ArrayList arrayList = new ArrayList(Arrays.asList(arr));

java.util.ArrayList中包含一个可以接受集合类型参数的构造函数。因为java.util.Arrays.ArrayList这个内部类继承了AbstractList类,所以,该类也是Collection的子类。

  1. 判断一个数组是否包含某个值

在判断一个数组中是否包含某个值的时候,开发者经常这样做:

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

以上方式虽然可以实现功能,但是效率却比较低。因为将数组压入Collection类型中,首先要将数组元素遍历一遍,然后再使用集合类做其他操作。可以使用下面方式实现:

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

或者

for(String s: arr){
    if(s.equals(targetValue))
        return true;
}
return false;
  1. 在循环中删除列表中的元素

先看看下面这段循环中移除元素的代码:

ArrayList list = new ArrayList(Arrays.asList("a","b","c","d"));
for(int i=0;i

输出结果:

[b,d]

这个方法出现了1个严重的问题,当一个元素被删除时,列表的大小缩小并且下标也会随之变化,所以当你想要在一个循环中用下标删除多个元素的时候,它并不会正常的生效。

也有些人知道以上代码的问题就由于数组下标变换引起的。所以,他们想到使用增强for循环的形式:

ArrayList list = new ArrayList(Arrays.asList("a","b","c","d"));
for(String s:list){
    if(s.equals("a")){
        list.remove(s);
    }
}

但是,很不幸的是,以上代码会抛出ConcurrentModificationException

想实现这个功能,可以使用如下代码:

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

next()方法必须在调用remove()方法之前调用。第1个示例,使用了foreach,编译器会调用.next()方法,正是这个方法抛出的ConcurrentModificationException,感兴趣的可以看一下 ArrayList.iterator() 的源码。

  1. HashTable 和 HashMap 的选择

了解算法的人可能对HashTable比较熟悉,因为他是一个数据结构的名字。但在Java里边,用HashMap来表示这样的数据结构。Hashtable和 HashMap的一个关键性的不同是,HashTable是同步的,而HashMap不是。所以通常不需要HashTable,HashMap用的更多。

  1. 使用原始集合类型

在Java里边,原始类型和无界通配符类型很容易混合在一起。以Set为例,Set是一个原始类型,而Set< ? >是一个无界通配符类型。 (可以把原始类型理解为没有使用泛型约束的类型)

考虑下面使用原始类型List作为参数的代码:

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

上面的代码将会抛出异常:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

使用原始集合类型是很危险的,因为原始集合类型跳过了泛型类型检查,是不安全的。Set、Set< ? >和Set< Object >之间有很大差别。

  1. 访问级别

程序员们经常使用public作为类中的字段的修饰符,因为这样可以很简单的通过引用得到值,但这并不是好的设计,按照经验,分配给成员变量的访问级别应该尽可能的低。

  1. ArrayListLinkedList的选择

当程序员们不知道ArrayListLinkedList的区别时,他们经常使用ArrayList,因为它看起来比较熟悉。然而,它们之间有巨大的性能差别。简而言之,如果有大量的增加删除操作并且没有很多的随机访问元素的操作,应该首先LinkedList。(LinkedList更适合从中间插入或者删除(链表的特性))

  1. 可变与不可变

不可变对象有许多的优点,比如简单,安全等等。同时,也有人提出疑问:既然不可变有这么多好处,为什么不把所有类都搞成不可变的呢?

通常情况下,可变对象可以用来避免产生过多的中间对象。一个经典的实例就是连接大量的字符串,如果使用不可变的字符串,将会产生大量的需要进行垃圾回收的对象。这会浪费CPU大量的时间,使用可变对象才是正确的方案(比如StringBuilder)。

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

StackOverflow中也有关于这个的讨论。

  1. 父类和子类的构造函数
class Super {
    String s;
    public Super(String s) {
        this.s = s;
    }
}

public class Sub extends Super {
    int x = 200;
    public Sub(String s) {

    }

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

    public static void main(String[] args) {
        Sub s = new Sub();
    }

}

上面的代码中有两处编译时错误,原因其实很简单,主要和构造函数有关。首先,我们都知道:

  • 如果一个类没有定义构造函数,编译器将会插入一个无参数的默认构造函数。
  • 如果一个类中定义了一个带参数的构造函数,那么编译器就不会再帮我们创建无参的构造函数。

上面的Super类中定义了一个带参数的构造函数。编译器将不会插入默认的无参数构造函数。

我们还应该知道:

  • 子类的所有构造函数(无论是有参还是无参)在执行时,都会调用父类的无参构造函数。

所以,编译器试图调用Super类中的无参构造函数。但是父类默认的构造函数未定义,编译器就会报出这个错误信息。

要解决这个问题,可以简单的通过

  1. 在父类中添加一个Super()构造方法,就像这样:

    public Super(){}

  2. 移除自定义的父类构造函数

  3. 在子类的构造函数中调用父类的super(value)。

  1. ""还是构造函数

创建 String 对象有2种方式:

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

2种方式有什么不同? 下面的例子也许对你有所帮助:

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

如果你只需要创建一个字符串,你可以使用双引号的方式,如果你需要在堆中创建一个新的对象,你可以选择构造函数的方式。

英文原文中有很多扩展链接,这里并没给出,后面我会挑选一些翻译一下。

你可能感兴趣的:(10个 Java 开发者常犯的错误)