纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

Java开发中的bug Java开发中最让人头疼的十个bug

程序员cxuan   2021-10-09 我要评论
想了解Java开发中最让人头疼的十个bug的相关内容吗程序员cxuan在本文为您仔细讲解Java开发中的bug的相关知识和一些Code实例欢迎阅读和指正我们先划重点:java开发中的坑,java,bug大全,java中遇到的问题下面大家一起来学习吧。

前言

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

如果你不小心看到同事的代码出现了我所描述的这些 bug 后那你就把我这篇文章甩给他!!!

废话不多说下面进入正题。

错误一:Array 转换成 ArrayList

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

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

如果要将数组转换为 ArrayList我们一般的做法会是这样

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

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

因为 ArrayList 的构造方法是可以接收一个 Collection 集合的所以这种创建方式是可行的。

错误二:检查数组是否包含某个值

检查数组中是否包含某个值部分程序员经常会这么做:

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

这段代码虽然没错但是有额外的性能损耗正常情况下不用将其再转换为 set直接这么做就好了:

return Arrays.asList(arr).contains(targetValue);

或者使用下面这种方式(穷举法循环判断)

for(String s: arr){
	if(s.equals(targetValue))
		return true;
}
return false;

上面第一段代码比第二段更具有可读性。

错误三:在 List 中循环删除元素

这个错误我相信很多小伙伴都知道了在循环中删除元素是个禁忌有段时间内我在审查代码的时候就喜欢看团队的其他小伙伴有没有犯这个错误。

说到底为什么不能这么做(集合内删除元素)呢?且看下面代码

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 一个元素后其列表的内部结构会发生改变一开始集合总容量是 4remove 一个元素之后就会变为 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 进行元素的添加或者删除操作一定要使用迭代器进行删除。也就是

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 中数据结构的名称是 HashMapHashtable 和 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>等类型在编译后都会变成ListJVM 看到的只是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 的时间和精力使用可变对象是正确的解决方案(例如

String result="";
for(String s: arr){
	result = result + s;
}

所以正确选择可变对象还是不可变对象需要慎重抉择。

错误九:构造函数

首先看一段代码分析为什么会编译不通过?

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

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

public Super(){
    System.out.println("Super");
}

错误十:到底是使用 "" 还是构造函数

考虑下面代码:

String x = "abc";
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

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

后记

今天我给你汇总了一下 Java 开发中常见的 10 个错误虽然比较简单但是很容易忽视的问题细节成就完美看看你还会不会再犯了如果再犯嘿嘿嘿。


相关文章

猜您喜欢

  • python的继承 python的继承详解

    想了解python的继承详解的相关内容吗兔子看日落在本文为您仔细讲解python的继承的相关知识和一些Code实例欢迎阅读和指正我们先划重点:python继承,继承下面大家一起来学习吧。..
  • MySQL索引下推 MySQL索引下推详细

    想了解MySQL索引下推详细的相关内容吗程序员巴士在本文为您仔细讲解MySQL索引下推的相关知识和一些Code实例欢迎阅读和指正我们先划重点:MySQL索引下推,MySQL索引下面大家一起来学习吧。..

网友评论

Copyright 2020 www.Shellfishsoft.com 【贝软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式