2015-05-09 66 views
4

我收到用户的崩溃报告,似乎是不可能的。 stacktrace表明一个对象为null,并且他得到了一个nullpointerException。在完全创建对象之前可以调用对象方法吗?

(这里是行,如果你想看到)

public class City extends Unit { 
    private ArrayList<SolderType> Queue = new ArrayList<SolderType>(); 

    public float getPrecentCompleted() 
    { 
     if(Queue.isEmpty()) 
     { 
      return 0f; 
     } 
     //More code that is not relevent 
    } 
} 

它不会留下太大的解释队列可能是空,但队列是在我的代码,一个地方只能创建,这是在构造函数中。所以我不明白它是如何可以为空。该对象在多于一个线程上共享,并且始终创建新对象。但队列点只能在创建对象时设置。所以我不明白这是可能的。一个线程可以调用一个对象方法,而另一个线程可以创建对象,但没有完成?

编辑增加了一些可能与问题相关的代码。

+1

是的,这是可能的,除非你添加了同步来防止它。 –

+0

也许吧。但也许它应该是最终的或不稳定的。而且由于它只在创作时分配,所以应该是最终的。 –

+2

很高兴看到构造函数体以及完整的异常堆栈跟踪。 –

回答

2

该评论给出了正确的答案:是的,这是可能的,除非你添加了同步来阻止它。执行的操作只有至极限定两个线程之间的顺序如下(见https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4):

  1. 上监视器M的解锁动作同步-与所有后续锁米 操作(其中,“随后的”根据定义 同步顺序)。
  2. 写入易失性变量v(§8.3.1。4)与任何线程(其中“后续”根据同步顺序被定义为 )的后续v读取同步。
  3. 启动一个线程的动作同步,与 的第一项行动,开始线程。
  4. 默认值(零,false或null)到每个 变量的写入同步,在每个线程的第一个动作。虽然在分配包含变量的对象之前向变量写入默认值似乎有点奇怪,但从概念上讲,每个对象都是在程序开始时以其默认初始化值创建的。
  5. 在一个线程T1的最后动作同步-与 任何行动,其检测T1已经终止另一个线程T2。
  6. 如果线程T1中断线程T2,由T1 中断同步-与任何点处的任何其他线程(包括T2) 确定T2已经被中断(由具有 InterruptedException的抛出或通过调用Thread.interrupted或 Thread.isInterrupted)。

没有这样的操作,你会看到其他线程中的对象处于未定义状态。

上的方式来解决空指针异常是使用最后一个字段(见https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5)。但是你也必须同步ArrayList。我建议使用java.util.concurrent包中的一个队列。

2

这种情况的最常见的原因是从调用父类的构造一个覆盖的方法是这样的:

import java.util.ArrayList; 


public class NPEInheritance { 
    static class Parent { 
     Parent() { 
      validate(); 
     } 

     void validate() {} 
    } 

    static class Child extends Parent { 
     private ArrayList<Object> Queue; 

     Child() { 
      Queue = new ArrayList<>(); 
     } 

     @Override 
     void validate() { 
      if(Queue.isEmpty()) { 
       System.out.println("Queue is empty"); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     new Child(); 
    } 
} 

当你运行这段代码,你会看到这样的“不可能NullPointerException”。正如你所看到的,这里Parent构造函数调用在Child类中被覆盖的方法,并且覆盖方法使用未被初始化的字段,因为Child构造函数仍未执行。

+0

感谢您的提示,但这不是问题,因为它是在声明的位置创建的,并且包含在所有构造函数中。 – Frozendragon

0

我认为当你创建子对象时,它首先要做的就是初始化父对象(因为它的扩展),当你的父对象试图验证它的对象时,你会得到NullPointer。 我觉得你需要把你的Queue对象放入父类,就像这样。

import java.util.ArrayList; 

public class NPEInheritance { 
    static class Parent { 

     protected ArrayList<Object> Queue; 

     Parent() { 
      Queue = new ArrayList<>(); 
      validate(); 
     } 

     void validate() {} 
    } 

    static class Child extends Parent { 

     Child() { 
     } 

     @Override 
     void validate() { 
      if(Queue.isEmpty()) { 
       System.out.println("Queue is empty"); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     new Child(); 
    } 
} 
0

特殊情况需要提防出现时,A类的静态初始化创建或引用B类(可能是静态的)情况下,和B的静态初始化对A.

类似的依赖性显然,在这种情况下,两个班都不能完全初始化。

Java通过允许其中一个类返回未初始化的值来静静地打破死锁。因此,如果没有警告,您可能会看到“不可能”的零或空值。由于这个依赖循环可能会经过其他几个类,因此即使知道存在这种风险,追查也是非常痛苦的。

修复通常是将一些或所有涉及到的静态对象重构为第三个类,从而使依赖关系图再次成为树。一旦你发现问题,简单明了。当然,更好的做法是首先避免造成问题。

相关问题