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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值