Java开发者常犯的10个错误

来源:互联网 发布:c语言中assert 编辑:程序博客网 时间:2024/04/30 01:12

Java开发者常犯的10个错误

下面列表总结了Java开发者经常犯的10个错误。

1.将Array转换为ArrayList

将数组转换为ArrayList,经常这样做:

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

Arrays.asList()会返回一个ArrayList,但返回的这个ArrayList是Arrays类内部的一个静态私有类,而不是java.util.ArrayList类。java.until.Arrays.ArrayList类有set(),get(),contain()方法,但是没有添加元素的任何方法,所以它的大小是固定的。所以为了创建一个真实的ArrayList,应该:

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

ArrayList的构造器可以接受一个集合类型,它也是java.util.Arrays.ArrayList的超类。

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

开发时经常这样做:

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

代码是正常运行的,但是将一个list转换为set是没有必要的。将list转换为set需要花费额外的时间。所以这可以优化为:

    Arrays.asList(arr).contains(targetValue);    //或者    for(String s:arr){        if(s.equals(targetValue))            return true;    }    return false;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当然,第一个比第二个是更可读的。

3. 在循环里面从一个List里移出元素

思考下面这样一段代码,在遍历期间移出元素:

   ArrayLisy<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d"));    for(int i = 0;i < list.size();i++){        list.remove(i);    }    System.out.print(list);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

上面的输出是:

[b,d]
  • 1
  • 1

这是一个非常严重的问题。当一个元素移出的时候,list的大小变小,索引改变,所以如果想在循环里通过索引来删除多个元素是不合适的。

在循环中使用iterator遍历器才是删除元素的正确方式。你可能又会想到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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

但是上面的代码会抛出 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"))            list.remove(s);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

.next()必须在.remove()之前调用。在foreach循环中,编译器会在移出元素操作之后调用.next(),这就引起了ConcurrentModificationException异常。可以参考ArrayList.iterator()的源码。

4. Hashtable和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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的代码会抛出一个异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at ...
  • 1
  • 1

当原始类型集合跳过一般类型检验时,使用原始类型集合是危险且不安全的。Set,Set<\?> 和 Set<0bject>的不同: 
Set和Set<\?>都能持有任何类型的元素。Set<\?>有如下两方面的含义: 
- ?标记代表任何类型,Set<\?>可以持有任何类型的元素 
- 由于我们不知道?的类型,所以我们不能put任何元素到Set<\?>集合中

    //合法代码    public static void main(String[] args) {        HashSet<Integer> s1 = new HashSet<Integer>(Arrays.asList(1, 2, 3));        printSet(s1);        HashSet<String> s2 = new HashSet<String>(Arrays.asList("a", "b", "c"));        printSet(s2);    }    public static void printSet(Set<?> s) {        for (Object o : s) {            System.out.println(o);        }    }    //非法代码    public static void printSet(Set<?> s) {        s.add(10);//该行是非法的        for (Object o : s) {            System.out.println(o);        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

由于不知道<\?>具体类型,所以不能添加任何东西到Set<\?>,因此也不能使用Set<\?>初始化一个set。比如下面的代码是非法的:

    //非法代码    Set<?> set = new HashSet<?>();
  • 1
  • 2
  • 1
  • 2

6 .域可访问性

开发者经常使用public作为类域的修饰符。这样很容易通过直接引用得到域值,但是这是一个非常坏的设计。好的规则是给成员尽可能低的访问权限。

    public, default, protected, and private
  • 1
  • 1

7. ArrayList和LinkedList

当开发者不知道二者区别的时候,经常使用ArrayList,因为它看起来更常见。然而,它们之间有一个很大的性能差距。总的来说,如果有大量的aad/remove操作且没有太多的随机访问操作时LinkedList表现得更好。

8. Mutable和Immutable

不可变对象有很多好处,比如简单,安全等。但是它要求每个确切的值有一个单独的对象,太多的对象可能引起大量的垃圾回收。所以在选择可变与不可变时,应该有一个权衡。 
一般 ,可变对象常用来避免产生太多的中间对象。一个经典例子是拼接大量的字符串。如果使用不可变的string,就会产生许多的对象,从而立刻引起垃圾回收。浪费了CPU的时间和资源,所以使用一个可变对象才是正确的方案(比如StringBuilder)。 
String result=”“; 
for(String s: arr){ 
result = result + s; 

也有其他适合可变对象的场景。例如 传递可变对象给方法,从而聚合多个结果,而不是通过很多句法。另外一个例子是排序和过滤,当然你可能创建一个方法来负责原始集合并返回排序集合,但是这可能会引起额外的垃圾回收。

9. 超类构造器和子类构造器

父类和子类构造器的关系 
上面例子中将父类的默认构造器显式表达出来就好了。

public Super(){    System.out.println("Super");}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

10. “”还是使用构造器

创建字符串有两种方式:使用双引号和使用构造器

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

这二者有什么不同呢?看下面这个例子:

 String a = "abcd";    String b = "abcd";    System.out.println(a == b);  // True a b指向相同的字面字符串,内存引用是相同的    System.out.println(a.equals(b)); // True    String c = new String("abcd");    String d = new String("abcd");    System.out.println(c == d);  // False 在堆内存中c和d指向了不同的对象,不同的对象总是有不同的内存引用。    System.out.println(c.equals(d)); // True
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这主要考虑它们在内存中是如何分配的。当相同的字面字符串被多次创建时,只有每个确切值的一个拷贝被存储。这种现象叫做string interning。在Java中所有编译时常量字符串会自动被intern。 
字符创创建时的内存分配 
运行时,string的interning 
string 的interning在运行时也是可以做的,尽管两个字符串都是使用构造器构造的。看下面的代码:

 String c = new String("abcd").intern();    String d = new String("abcd").intern();    System.out.println(c == d);  // Now true    System.out.println(c.equals(d)); // True
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

由于字面字符串已经是类型化的String,使用构造器会创建额外的不必须的对象。所以如果只需要创建一个String,应该使用双引号方式。如果需要在堆栈中创建一个新的对象,则应该使用构造器方式。

0 0
原创粉丝点击