Java开发者常犯的十个错误

翻译自:Top 10 Mistakes Java Developers Make java

 

文章列出了Java开发者最常犯的是个错误。算法

 

1.将数组转换为ArrayList

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

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

Arrays.asList()会返回一个ArrayList,但这个ArrayListArrays的私有静态类,不是java.util.ArrayListjava.util.Arrays.ArrayListset(), get(), contains()方法,但没有任何能增长元素的方法,因此它的大小是肯定的。
为了建立一个真正的ArrayList,应该这样作:安全

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

ArrayList的构造函数可以接收一个Collection类型,而它也是java.util.Arrays.ArrayList的一个祖先类。markdown

 

2.检查一个数组是否包含某个值

开发者常常这样作:数据结构

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

这个的代码能够工做,但不必首先把数组转换为集合,把数组转换为集合须要额外的时间。能够这样作:函数

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

或者性能

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

第一个比第二个的可读性更好。ui

 

3.在循环里边删除列表的元素

思考下面的代码,该代码在循环里边删除元素翻译

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]

上面的方法有一个严重的问题。当一个元素被移除后,列表的大小减少了,索引也就变了。因此但愿利用索引在一个循环里边删除多个元素是作不到的。
你可能知道利用迭代器在一个循环里边删除元素是正确的方法,而且知道Java的foreach循环很像一个迭代器,但事实上不是。思考下面的代码:

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循环里边,编译器会先调用.remove(),再调用.next(),从而致使异常ConcurrentModificationException。你可能想知道ArrayList.iterator()的源代码。

 

4.HashTable vs HashMap

根据算法的约定,HashTable是这个数据结构的名字,但在Java里边,HashMap是这个数据结构的名字。Hashtable和 HashMap的一个关键性的不一样是,HashTable是同步的,而HashMap不是。因此一般不须要HashTable,HashMap用的更多。
HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap
Top 9 questions about Java Maps

 

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 ...

使用原始集合类型是很危险的,由于原始集合类型跳过了泛型类型检查,是不安全的。SetSet< ? >Set< Object >之间有很大差异。请参考Raw type vs. Unbounded wildcardType Erasure

 

6.访问级别

开发者常用Public做为类的修饰符,这样能够很简单的经过直接引用获得值,但这是一个很是糟糕的设计。根据经验,分配给成员的访问级别应尽量的低。
public, default, protected, and private

 

7.ArrayList vs LinkedList

当开发者不知道ArrayList和LinkedList的区别的时候,一般会使用ArrayList,由于它看起来更熟悉。然而,二者之间有很大 的性能差别。简单地说,当有大量的插入/删除操做而且没有太多的随机访问操做的时候,应该使用LinkedList。若是对此不太了解,可参考ArrayList vs. LinkedList

 

8.可变与不可变

不可变对象有许多的优势,好比简单,安全等等。可是对于每个不一样的值都要有一个独立的对象,过多的对象致使垃圾回收的高消耗。当选择可变与不可变时应该有一个权衡。
一般状况下,可变对象能够用来避免产生过多的中间对象。一个经典的实例就是链接大量的字符串,若是使用不可变的字符串,将会产生大量的须要进行垃圾回收的对象。这会浪费CPU大量的时间,使用可变对象才是正确的方案(好比StringBuilder)。

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

在其它的一些状况下也是须要使用可变对象的,好比将可变对象做为参数传入方法可使你不须要使用不少语句即可以获得多个结果。另一个例子是排序和 过滤:固然,你能够写一个方法来接收原始的集合,而且返回一个排好序的集合,可是那样对于大的集合就太浪费了。(来自StackOverFlow的dasblinkenlight的答案)。
Why String is Immutable?

 

9.父类和子类的构造函数


由于没有定义父类的默认构造函数,在编译的时候会产生错误。在Java里边,若是一个类没有定义构造函数,编译器将会插入一个无参数的默认构造函数。若是在父类里边定义了一个构造函数,在此例中即Super(String s),编译器将不会插入默认的无参数构造函数。这就是上面示例中父类的情形。
子类的构造函数,不论是没有参数还有有参数,都会调用父类的无参构造函数。由于编译器试图把super()插入到子类的两个构造函数中。可是父类默认的构造函数未定义,编译器就会报出这个错误信息。
要修复这个问题,能够简单的经过1)在父类中添加一个Super()构造方法,就像这样:

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

或者2)移除自定义的父类构造函数,或者3)在子类的构造函数中调用父类的super(value)。
Constructor of Super and Sub

 

10.”“仍是构造函数?

有两种方式构造字符串:

//1. 利用双引号
String x = "abc";
//2. 利用构造函数
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

 

关于它们内存分配的更多细节,请参考Create Java String Using ”” or Constructor?

 

未来的工做

这个列表是我根据大量的GitHub上的开源项目、Stack Overflow上的问题和一些常见的Google搜索获得的。没有明显示的评估证实它们是准确的前10,但它们绝对是很常见的问题。若是您不一样意任一部 分,请留下您的评论。若是您能提出其它一些常见的错误,我将会很是感激。

翻译自:Top 10 Mistakes Java Developers Make

相关文章
相关标签/搜索