关于java:Java-开发最容易写的-10-个bug

3次阅读

共计 4981 个字符,预计需要花费 13 分钟才能阅读完成。

那个谁,明天又写 bug 了,没错,他说的如同就是我。。。。。。

作为 Java 开发,咱们在写代码的过程中难免会产生各种奇思妙想的 bug,有些 bug 就挺让人无奈的,比如说各种空指针异样,在 ArrayList 的迭代中进行删除操作引发异样,数组下标越界异样等。

如果你不小心看到共事的代码呈现了我所形容的这些 bug 后,那你就把我这篇文章甩给他!!!你甩给他一篇文章,并让他关注了一波 cxuan,你会播种他在前面像是如获至宝并满眼崇拜大神的眼光。

废话不多说,上面进入正题。

谬误一:Array 转换成 ArrayList
Array 转换成 ArrayList 还能出错?这是哪个笨。。。。。。

等等,你先别着急说,先来看看是怎么回事。

如果要将数组转换为 ArrayList,咱们个别的做法会是这样

List<String> list = Arrays.asList(arr);
1
Arrays.asList() 将返回一个 ArrayList,它是 Arrays 中的公有动态类,它不是 java.util.ArrayList 类。如下图所示

Arrays 外部的 ArrayList 只有 set、get、contains 等办法,然而没有可能像是 add 这种可能使其内部结构进行扭转的办法,所以 Arrays 外部的 ArrayList 的大小是固定的。

如果要创立一个可能增加元素的 ArrayList,你能够应用上面这种创立形式:

ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(arr));
1
因为 ArrayList 的构造方法是能够接管一个 Collection 汇合的,所以这种创立形式是可行的。

谬误二:查看数组是否蕴含某个值
查看数组中是否蕴含某个值,局部程序员常常会这么做:

Set<String> set = new HashSet<String>(Arrays.asList(arr));
return set.contains(targetValue);
1
2
这段代码尽管没错,然而有额定的性能损耗,失常状况下,不必将其再转换为 set,间接这么做就好了:

return Arrays.asList(arr).contains(targetValue);
1
或者应用上面这种形式(穷举法,循环判断)

for(String s: arr){

if(s.equals(targetValue))
    return true;

}
return false;

下面第一段代码比第二段更具备可读性。

谬误三:在 List 中循环删除元素
这个谬误我置信很多小伙伴都晓得了,在循环中删除元素是个禁忌,有段时间内我在审查代码的时候就喜爱看团队的其余小伙伴有没有犯这个谬误。

说到底,为什么不能这么做(汇合内删除元素)呢?且看上面代码
https://www.doc88.com/p-67216…
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]

为什么只有两个值?我这不是循环输入的么?

其实,在列表外部,当你应用内部 remove 的时候,一旦 remove 一个元素后,其列表的内部结构会产生扭转,一开始汇合总容量是 4,remove 一个元素之后就会变为 3,而后再和 i 进行比拟判断。。。。。。所以只能输入两个元素。

你可能晓得应用迭代器是正确的 remove 元素的形式,你还可能晓得 for-each 和 iterator 这种工作形式相似,所以你写下了如下代码

ArrayList<String> list = new ArrayList<String>(Arrays.asList(“a”, “b”, “c”, “d”));

for (String s : list) {

if (s.equals("a"))
    list.remove(s);

}

而后你充斥自信的 run xxx.main() 办法,后果。。。。。。ConcurrentModificationException

为啥呢?

那是因为应用 ArrayList 中内部 remove 元素,会造成其内部结构和游标的扭转。

在阿里开发标准上,也有不要在 for-each 循环内对元素进行 remove/add 操作的阐明。

所以大家要应用 List 进行元素的增加或者删除操作,肯定要应用迭代器进行删除。也就是
https://www.doc88.com/p-67216…
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 循环中,编译器会在删除元素的操作后调用 .next(),导致 ConcurrentModificationException。

谬误四:Hashtable 和 HashMap
这是一条算法方面的规约:依照算法的约定,Hashtable 是数据结构的名称,然而在 Java 中,数据结构的名称是 HashMap,Hashtable 和 HashMap 的次要区别之一就是 Hashtable 是同步的,所以很多时候你不须要 Hashtable,而是应用 HashMap。

谬误五:应用原始类型的汇合
这是一条泛型方面的束缚:

在 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);

}

这段代码会抛出 java.lang.ClassCastException 异样,为啥呢?

应用原始类型汇合是比拟危险的,因为原始类型会跳过泛型查看而且不平安,Set、Set<?> 和 Set<Object> 存在微小的差别,而且泛型在应用中很容易造成类型擦除。

大家都晓得,Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是了解类型擦除。Java 的泛型基本上都是在编译器这个档次上实现的,在生成的字节码中是不蕴含泛型中的类型信息的,应用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

如在代码中定义 List<Object> 和 List<String> 等类型,在编译后都会变成 List,JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 是看不到的。Java 编译器会在编译时尽可能的发现可能出错的中央,然而依然无奈在运行时刻呈现的类型转换异样的状况,类型擦除也是 Java 的泛型与 C++ 模板机制实现形式之间的重要区别。

比方上面这段示例

public class Test {

public static void main(String[] args) {ArrayList<String> list1 = new ArrayList<String>();
    list1.add("abc");

    ArrayList<Integer> list2 = new ArrayList<Integer>();
    list2.add(123);

    System.out.println(list1.getClass() == list2.getClass());
}

}

在这个例子中,咱们定义了两个 ArrayList 数组,不过一个是 ArrayList<String> 泛型类型的,只能存储字符串;一个是 ArrayList<Integer> 泛型类型的,只能存储整数,最初,咱们通过 list1 对象和 list2 对象的 getClass()办法获取他们的类的信息,最初发现后果为 true。阐明泛型类型 String 和 Integer 都被擦除掉了,只剩下原始类型。

所以,最下面那段代码,把 10 增加到 Object 类型中是齐全能够的,然而将 Object 类型的“10”转换为 String 类型就会抛出类型转换异样。

谬误六:拜访级别问题
我置信大部分开发在设计 class 或者成员变量的时候,都会简略粗犷的间接申明 public xxx,这是一种蹩脚的设计,申明为 public 就很容易赤身裸体,这样对于类或者成员变量来说,都存在肯定危险性。

谬误七:ArrayList 和 LinkedList
哈哈哈,ArrayList 是我见过程序员应用频次最高的工具类,没有之一。

当开发人员不晓得 ArrayList 和 LinkedList 的区别时,他们常常应用 ArrayList(其实实际上,就算晓得他们的区别,他们也不必 LinkedList,因为这点性能不值一提),因为看起来 ArrayList 更相熟。。。。。。

然而实际上,ArrayList 和 LinkedList 存在微小的性能差别,简而言之,如果增加 / 删除操作大量且随机拜访操作不是很多,则应首选 LinkedList。如果存在大量的拜访操作,那么首选 ArrayList,然而 ArrayList 不适宜进行大量的增加 / 删除操作。

谬误八:可变和不可变
不可变对象有很多长处,比方简略、平安等。然而不可变对象须要为每个不同的值调配一个独自的对象,对象不具备复用性,如果这类对象过多可能会导致垃圾回收的老本很高。在可变和不可变之间进行抉择时须要有一个均衡。

一般来说,可变对象用于防止产生过多的两头对象。比方你要连贯大量字符串。如果你应用一个不可变的字符串,你会产生很多能够立刻进行垃圾回收的对象。这会节约 CPU 的工夫和精力,应用可变对象是正确的解决方案(例如 StringBuilder)。如下代码所示:

String result=””;
for(String s: arr){

result = result + s;

}
1
2
3
4
所以,正确抉择可变对象还是不可变对象须要谨慎抉择。

谬误九:构造函数
首先看一段代码,剖析为什么会编译不通过?

产生此编译谬误是因为未定义默认 Super 的构造函数。在 Java 中,如果一个类没有定义构造函数,编译器会默认为该类插入一个默认的无参数构造函数。如果在 Super 类中定义了构造函数,在这种状况下 Super(String s),编译器将不会插入默认的无参数构造函数。这就是下面 Super 类的状况。

要想解决这个问题,只须要在 Super 中增加一个无参数的构造函数即可。

public Super(){

System.out.println("Super");

}
1
2
3
谬误十:到底是应用“”还是构造函数
思考上面代码:

String x = “abc”;
String y = new String(“abc”);
1
2
下面这两段代码有什么区别吗?

可能上面这段代码会给出你答复

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

这就是一个典型的内存调配问题。

后记
明天我给你汇总了一下 Java 开发中常见的 10 个谬误,尽管比较简单,然而很容易漠视的问题,细节成就完满,看看你还会不会再犯了,如果再犯,嘿嘿嘿。
————————————————
原文链接:https://www.doc88.com/p-67216…

正文完
 0