下面总结了10个Java程序员最易犯的错误.
将一个数组array转换为ArrayList,程序猿通常这样做:
List<String> list = Arrays.asList(arr);
Arrays.asList() 会返回一个Arrays的内部私有静态类的对象.该内部类实现了List接口,而不是java.util.ArrayList.java.util.Arrays.ArrayList类包含方法:set(), get(), contains(),但是不包含添加元素的方法,因此该list对象的定长的.
如果要创建一个真正的ArrayList,你需要以下操作:
ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
ArrayList的构造函数可以接收Collection类型,Collection同样是java.util.Arrays.ArrayList的基类.
可以通过以下方式实现:
Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);
以上代码是可用的, 但是没有必要先将List转化为Set,这样会带来额外的时间开销. 因此,可以简化为以下方式:
Arrays.asList(arr).contains(targetValue);
或者
for(String s: arr){
if(s.equals(targetValue))
return true;
}
return false;
相较于第二段代码,第一段有更好的可读性.
以下代码为在循环迭代中移除元素:
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]
这个方法中有一个严重的问题.当元素被移除后,list的大小和索引会发生变化. 因此,如果想通过遍历索引来删除多个集合中的元素,以上代码不会起到相应的作用.
你或许已经了解可以通过迭代器来删除元素,并且你也知道增强型for循环的工作原理就是迭代器.
但是实际上这是错误的.参考以下代码:
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()之前. 在增强for循环中,编译器将会使.next()的调用发生在remove操作之后, 这将会引发ConcurrentModificationException. 如果有兴趣,可用参考ArrayList.iterator()的源码.
依照算法中的约定,Hashtable是一种数据结构的名称.但是在Java中,这种数据结果的名称是HashMap.Hashtable和HashMap一个最重要的区别在于Hashtable是线程安全的.因此,通常你不会用到Hashtable,取而代之的是应该应用HashMap.
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);
}
这段代码将抛出以下异常:
Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at … 使用原生类型是很危险的,因为原生类型的集合会跳过泛型检查且不安全.因此,Set, Set<?> 和 Set
程序猿们通常会使用public来修饰类属性. 这样便于通过直接引用来获取属性值, 但这却是一个很糟糕的设计. 最佳实践应为:尽可能使用低级别的访问权限.
public, default, protected, 和private
当程序猿们不了解ArrayList和LinkedList的区别时候, 他们通常会使用ArrayList, 应为它看起来更加熟悉. 然后, 他们之间会有巨大的性能差别. 大致上, 在有大量增/删操作,并且没有频繁随机取值的前提下, LinkedList是一个更优的选择.
不可变对象有很多的有优点,诸如:简单, 安全等等. 但是对于每一个不同的值, 都需要一个单独的对象, 并且大量的对象会导致垃圾收集的巨大开销. 因此在可变与不可变对象的选择上,应该寻求一个平衡.
通常,可变对象被用于避免产生大量的中间对象. 一个经典的例子是利用可变对象串联大量的字符串. 如果你用不可变对象, 将会产生大量的,会立即被垃圾回收的对象. 这种情况造成大量的耗时与CPU的开销, 因此可变对象是正确的解决方案(例如:StringBuilder).
String result="";
for(String s: arr){
result = result + s;
}
还存在一些其他的场景会应用到可变对象. 例如:将可变对象传进方法中, 可以收集多个结果,而不需要经过大量的syntactic hoops. 另一个例子是排序和过滤: 当然, 你可以创建一个方法来保存原始集合,之后返回一个排序后的集合, 但是对于更大的集合来说,这种做法的成本很高(From dasblinkenlight’s answer on Stack Overflow).
由于没有定义父类的默认构造方法而引起的编译错误是很常见的. 在Java中, 如果一个类中没有定义构造方法, 编译器会默认天假一个无参的构造方法. 如果父类中定一个的构造器, 例如: Super(String s), 编译器则不会自动添加无参构造方法. 这就是上述父类的场景.
子类的构造方法, 有参与无参的方法都会调用父类无参构造方法. 由于编译器会尝试在子类的2个构造方法内添加super(), 但是父类中没有定义默认的构造方法, 编译器会产生编译错误. .. 为了修复此问题:
1) 在父类中添加Super() 构造方法:
public Super(){
System.out.println("Super");
}
2) 移除父类中自定义的构造方法remove. 3) 子类构造函数中调用super(value).
字符串可以通过两种方式进行创建:
//1. use double quotes
String x = "abc";
//2. use constructor
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
以上的列表,是基于我对于GitHub中大量开源项目,StackOverflow中提问以及谷歌中热门搜索的分析. 并没有依据来证明它们是就是top 10, 但是的确是非常普遍的问题.如果有任何疑问, 请在下方进行评论.非常感谢您来指出的一些更加常见的错误,