我有以下代码:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class NullDereference {
private static final ConcurrentMap<Integer, Object> MAP = new ConcurrentHashMap<>();
public static void main(String[] args) {
Object object = getObject(1);
if (object == null) {
Lock lock = new ReentrantLock();
lock.lock();
try {
lock.newCondition().await(1, TimeUnit.SECONDS);
object = new Object();
object = addObject(object); // [3]
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
} finally { // [1]
lock.unlock(); // [1]
}
}
System.out.println("class: " + object.getClass()); // [2]
}
private static Object getObject(int hashCode) {
return MAP.get(hashCode);
}
private static Object addObject(Object newObject) {
Object oldObject = MAP.putIfAbsent(newObject.hashCode(), newObject);
if (oldObject != null) {
return oldObject;
}
return newObject;
}
}
NetBeans在第[2]行显示关于"取消引用可能的空指针"的警告。我不知道为什么。我以为这是因为第[3]行,但当我评论第[3]行时,警告仍然存在。当我在第[2]行之前显式检查null值时,或者当我注释掉整个finally
语句(由[1]注释的行)时,警告将消失。
我分析了代码,认为这是假阳性。我说得对吗?
我不想对空指针做额外的检查。这个代码可能有什么问题?我可以在没有警告的情况下更改代码吗?
我可以重现这一点。肖特:你是对的,它看起来像是NetBeans的bug。Eclipse和IDEA在这里没有显示任何警告。
Long:发出"可能的null取消引用"警告并不是一个简单的静态分析,因为它需要仔细遍历所有可能的控制流路径(我实际上正在编写类似的分析器,所以我知道这有多难)。拥有finally
会使事情变得更加困难,因为在每个代码路径之后都会执行finally部分,然后将控制返回到原始代码。适当的控制流图必须复制finally块的几个副本,仅仅添加几个传入和传出边是不够的。我可以推测NetBeans在这部分做得不对。
以下是不正确的控制流程图草图:
[ try { lock.newCondition().await(...) ...} ]
/ |
/ |
/ |
Successful InterruptedException other exception
Execution | /
| /
| /
| /
[ finally { lock.unlock; } ]
/ |
/ |
/ |
| | |
[System.out] [throw RuntimeEx] [throw the original exception]
请注意,沿着这个图的边缘,您可以访问InterruptedException
或其他异常之后的最终System.out
语句。正确的图形必须制作三个finally块的副本:
[ try { lock.newCondition().await(...) ...} ]
/ |
/ |
/ |
Successful InterruptedException other exception
Execution | |
| | |
[finally_copy1] [finally_copy2] [finally_copy3]
| | |
| | |
[System.out] [throw RuntimeEx] [throw the original exception]
这样,只有在成功执行try
之后,当object
被确定分配时,才能到达System.out
语句。