搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > AAA软件学院 > 学习java中你遭遇过这些莫名错误吗?

学习java中你遭遇过这些莫名错误吗?

AAA软件学院 2019-01-21
举报


在编程时,开发者经常会遭遇各式各样莫名错误。近日,Sushil Das在 Geek On Java上列举了 Java 开发中常见的 5 个错误。

1、Null 的过度使用

避免过度使用 null 值是一个最佳实践。例如,更好的做法是让方法返回空的 array 或者 collection 而不是 null 值,因为这样可以防止程序抛出 NullPointerException。下面代码片段会从另一个方法获得一个集合:

List<String> accountIds = person.getAccountIds();  
for (String accountId : accountIds) {  
    processAccount(accountId);
}

当一个 person 没有 account 的时候,getAccountIds() 将返回 null 值,程序就会抛出 NullPointerException 异常。因此需要加入空检查来解决这个问题。如果将返回的 null 值替换成一个空的 list,那么 NullPointerException 也不会出现。而且,因为我们不再需要对变量 accountId 做空检查,代码将变得更加简洁。

当你想避免 null 值的时候,不同场景可能采取不同做法。其中一个方法就是使用 Optional 类型,它既可以是一个空对象,也可以是一些值的封装。

Optional<String> optionalString = Optional.ofNullable(nullableString);  
if(optionalString.isPresent()) {  
    System.out.println(optionalString.get());
}

事实上,Java8 提供了一个更简洁的方法:

Optional<String> optionalString = Optional.ofNullable(nullableString);  
optionalString.ifPresent(System.out::println);

Java 是从 Java8 版本开始支持 Optional 类型,但是它在函数式编程世界早已广为人知。在此之前,它已经在 Google Guava 中针对 Java 的早期版本被使用。

2、忽视异常

我们经常对异常置之不理。然而,针对初学者和有经验的 Java 程序员,最佳实践仍是处理它们。异常抛出通常是带有目的性的,因此在大多数情况下需要记录引起异常的事件。别小看这件事,如果必要的话,你可以重新抛出它,在一个对话框中将错误信息展示给用户或者将错误信息记录在日志中。至少,为了让其它开发者知晓前因后果,你应该解释为什么没有处理这个异常。

selfie = person.shootASelfie();  
try {  
    selfie.show();
} catch (NullPointerException e) {
    // Maybe, invisible man. Who cares, anyway?
}

强调某个异常不重要的一个简便途径就是将此信息作为异常的变量名,像这样:

try { selfie.delete(); } catch (NullPointerException unimportant) {  }

3、并发修改异常

这种异常发生在集合对象被修改,同时又没有使用 iterator 对象提供的方法去更新集合中的内容。例如,这里有一个 hats 列表,并想删除其中所有含 ear flaps 的值:

List<IHat> hats = new ArrayList<>();  
hats.add(new Ushanka()); // that one has ear flaps  
hats.add(new Fedora());  
hats.add(new Sombrero());  
for (IHat hat : hats) {  
    if (hat.hasEarFlaps()) {
        hats.remove(hat);
    }
}

如果运行此代码,ConcurrentModificationException 将会被抛出,因为代码在遍历这个集合的同时对其进行修改。当多个进程作用于同一列表,在其中一个进程遍历列表时,另一个进程试图修改列表内容,同样的异常也可能会出现。

在多线程中并发修改集合内容是非常常见的,因此需要使用并发编程中常用的方法进行处理,例如同步锁、对于并发修改采用特殊的集合等等。Java 在单线程和多线程情况下解决这个问题有微小的差别。

收集对象并在另一个循环中删除它们

直接的解决方案是将带有 ear flaps 的 hats 放进一个 list,之后用另一个循环删除它。不过这需要一个额外的集合来存放将要被删除的 hats。

List<IHat> hatsToRemove = new LinkedList<>();  
for (IHat hat : hats) {  
    if (hat.hasEarFlaps()) {
        hatsToRemove.add(hat);
    }
}
for (IHat hat : hatsToRemove) {  
    hats.remove(hat);
} 

使用Iterator.remove方法

这个方法更简单,同时并不需要创建额外的集合:

Iterator<IHat> hatIterator = hats.iterator();  
while (hatIterator.hasNext()) {  
    IHat hat = hatIterator.next();
    if (hat.hasEarFlaps()) {
        hatIterator.remove();
    }
}

使用ListIterator的方法

当需要修改的集合实现了 List 接口时,list iterator 是非常合适的选择。实现 ListIterator 接口的 iterator 不仅支持删除操作,还支持add和set操作。ListIterator 接口实现了 Iterator 接口,因此这个例子看起来和Iterator的remove方法很像。唯一的区别是 hat iterator 的类型和我们获得 iterator 的方式——使用listIterator()方法。下面的片段展示了如何使用 ListIterator.remove和ListIterator.add方法将带有 ear flaps 的 hat 替换成带有sombreros 的。

IHat sombrero = new Sombrero();  
ListIterator<IHat> hatIterator = hats.listIterator();  
while (hatIterator.hasNext()) {  
    IHat hat = hatIterator.next();
    if (hat.hasEarFlaps()) {
        hatIterator.remove();
        hatIterator.add(sombrero);
    }
}

使用 ListIterator,调用remove和add方法可替换为只调用一个set方法:

IHat sombrero = new Sombrero();  
ListIterator<IHat> hatIterator = hats.listIterator();  
while (hatIterator.hasNext()) {  
    IHat hat = hatIterator.next();
    if (hat.hasEarFlaps()) {
        hatIterator.set(sombrero); // set instead of remove and add
    }
}

使用Java 8中的stream方法

在 Java8 中,开发人员可以将一个 collection 转换为 stream,并且根据一些条件过滤 stream。这个例子讲述了 stream api 是如何过滤 hats 和避免ConcurrentModificationException。 hats = hats.stream().filter((hat -> !hat.hasEarFlaps()))

.collect(Collectors.toCollection(ArrayList::new));

Collectors.toCollection方法将会创建一个新的 ArrayList,它负责存放被过滤掉的 hats 值。如果过滤条件过滤掉了大量条目,这里将会产生一个很大的 ArrayList。因此,需要谨慎使用。

使用 Java 8 中的List.removeIf 方法

可以使用 Java 8 中另一个更简洁明了的方法—— removeIf方法:

hats.removeIf(IHat::hasEarFlaps);

在底层,它使用 Iterator.remove来完成这个操作。

使用特殊的集合

如果在一开始就决定使用CopyOnWriteArrayList而不是ArrayList,那就不会出现问题。因为 CopyOnWriteArrayList提供了修改的方法(例如 set,add,remove),它不会去改变原始集合数组,而是创建了一个新的修改版本。这就允许遍历原来版本集合的同时进行修改,从而不会抛出 ConcurrentModificationException异常。这种集合的缺点也非常明显——针对每次修改都产生一个新的集合。

还有其他适用于不同场景的集合,比如 CopyOnWriteSet和ConcurrentHashMap。

关于另一个可能可能在并发修改集合时产生的错误是,从一个 collection 创建了一个 stream,在遍历 stream 的时候,同时修改后端的 collection。针对 stream 的一般准则是,在查询 stream 的时候,避免修改后端的 collection。接下来的例子将展示如何正确地处理 stream:

List<IHat> filteredHats = hats.stream().peek(hat -> {  
    if (hat.hasEarFlaps()) {
        hats.remove(hat);
    }
}).collect(Collectors.toCollection(ArrayList::new));

peek方法收集所有的元素,并对每一个元素执行既定动作。在这里,动作即为尝试从一个基础列表中删除数据,这显然是错误的。为避免这样的操作,可以尝试一些上面讲解的方法。

名    称:郑州AAA教育软件学院

地    点二七区政通路25号(工商局对面)

电    话:186-3877-9892

Q    Q:491897620

官    网http://www.aaa-edu.com/

是一种鼓励 | 分享传递友谊

小编:小叶  审核:席海斌

版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《学习java中你遭遇过这些莫名错误吗?》的版权归原作者「AAA软件学院」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

举报