您的位置:首页 > 编程语言 > Java开发

Java开发者常犯的10个错误

2017-02-16 12:39 351 查看


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,应该使用双引号方式。如果需要在堆栈中创建一个新的对象,则应该使用构造器方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java