可以在完全创建对象之前调用对象方法吗?
Can a objects method be called before a object is fully created?
我收到了一位用户的崩溃报告,似乎不太可能。堆栈跟踪表明一个对象为 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
}
}
解释 Queue 可能为 null 并没有遗漏太多内容,但是 Queue 仅在我的代码中的一个位置创建,即在构造函数中。所以我不明白它怎么可能为空。该对象在多个线程上共享,并且一直在创建新对象。但是Queue点只能在创建对象的时候设置。所以我不明白这怎么可能。是否有可能一个线程调用对象方法,而另一个线程创建对象但未完成?
编辑 添加了更多可能与问题相关的代码。
造成这种情况的最常见原因是从 superclass 构造函数调用重写方法,如下所示:
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();
}
}
当你运行这段代码时,你会看到这样的"impossible NullPointerException
"。如您所见,此处 Parent
构造函数调用了 Child
class 中重写的方法,重写方法使用尚未初始化的字段,因为 Child
构造函数仍未执行.
我认为,当您创建子对象时,它首先会初始化父对象(因为它会扩展),并且当您的父对象尝试验证其时间时,您将获得 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();
}
}
当 class A 的静态初始化创建或引用 class B 的一个(可能是静态的)实例,并且 B 的静态初始化对 A 具有类似的依赖性时,会出现一个需要注意的特殊情况。
显然,在这种情况下,class 都不能先于另一个完全初始化。
Java 通过允许 class 之一成为 return 未初始化的值来静静地打破僵局。因此,在没有警告的情况下,您可能会看到 "impossible" 个零或空值。由于此依赖循环可能会经历其他几个 classes,即使您知道存在此风险,追踪起来也确实很痛苦。
修复通常是将涉及的部分或全部静态对象重构为第三个 class,使依赖图再次成为树。一旦发现问题,就简单明了。当然,最好从一开始就避免制造问题。
评论给出了正确答案:是的,这是可能的,除非您添加了同步来防止它。定义两个线程之间顺序的唯一操作如下(参见 https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4):
- 监视器 m 上的解锁操作与所有后续锁定同步
对 m 的操作(其中 "subsequent" 是根据
同步顺序)。
- 写入易失性变量 v (§8.3.1.4) 与所有同步
任何线程对 v 的后续读取(其中定义了 "subsequent"
根据同步顺序)。
- 启动线程的操作与中的第一个操作同步
它启动的线程。
- 将默认值(零、假或空)写入每个
变量与每个线程中的第一个动作同步。尽管在分配包含变量的对象之前将默认值写入变量似乎有点奇怪,但从概念上讲,每个对象都是在程序开始时使用其默认初始化值创建的。
- 线程 T1 中的最终操作与线程中的任何操作同步
另一个检测到 T1 已终止的线程 T2。
- 如果线程T1中断了线程T2,由T1中断
与任何其他线程(包括 T2)所在的任何点同步
确定 T2 已被中断(通过有一个
InterruptedException 抛出或通过调用 Thread.interrupted 或
Thread.isInterrupted).
如果没有这样的操作,您将看到其他线程中的对象处于未定义状态。
解决空指针异常的一种方法是使用 final 字段(参见 https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5)。但是你还必须同步 ArrayList。我建议使用 java.util.concurrent 包中的队列。
我收到了一位用户的崩溃报告,似乎不太可能。堆栈跟踪表明一个对象为 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
}
}
解释 Queue 可能为 null 并没有遗漏太多内容,但是 Queue 仅在我的代码中的一个位置创建,即在构造函数中。所以我不明白它怎么可能为空。该对象在多个线程上共享,并且一直在创建新对象。但是Queue点只能在创建对象的时候设置。所以我不明白这怎么可能。是否有可能一个线程调用对象方法,而另一个线程创建对象但未完成?
编辑 添加了更多可能与问题相关的代码。
造成这种情况的最常见原因是从 superclass 构造函数调用重写方法,如下所示:
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();
}
}
当你运行这段代码时,你会看到这样的"impossible NullPointerException
"。如您所见,此处 Parent
构造函数调用了 Child
class 中重写的方法,重写方法使用尚未初始化的字段,因为 Child
构造函数仍未执行.
我认为,当您创建子对象时,它首先会初始化父对象(因为它会扩展),并且当您的父对象尝试验证其时间时,您将获得 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();
}
}
当 class A 的静态初始化创建或引用 class B 的一个(可能是静态的)实例,并且 B 的静态初始化对 A 具有类似的依赖性时,会出现一个需要注意的特殊情况。
显然,在这种情况下,class 都不能先于另一个完全初始化。
Java 通过允许 class 之一成为 return 未初始化的值来静静地打破僵局。因此,在没有警告的情况下,您可能会看到 "impossible" 个零或空值。由于此依赖循环可能会经历其他几个 classes,即使您知道存在此风险,追踪起来也确实很痛苦。
修复通常是将涉及的部分或全部静态对象重构为第三个 class,使依赖图再次成为树。一旦发现问题,就简单明了。当然,最好从一开始就避免制造问题。
评论给出了正确答案:是的,这是可能的,除非您添加了同步来防止它。定义两个线程之间顺序的唯一操作如下(参见 https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4):
- 监视器 m 上的解锁操作与所有后续锁定同步 对 m 的操作(其中 "subsequent" 是根据 同步顺序)。
- 写入易失性变量 v (§8.3.1.4) 与所有同步 任何线程对 v 的后续读取(其中定义了 "subsequent" 根据同步顺序)。
- 启动线程的操作与中的第一个操作同步 它启动的线程。
- 将默认值(零、假或空)写入每个 变量与每个线程中的第一个动作同步。尽管在分配包含变量的对象之前将默认值写入变量似乎有点奇怪,但从概念上讲,每个对象都是在程序开始时使用其默认初始化值创建的。
- 线程 T1 中的最终操作与线程中的任何操作同步 另一个检测到 T1 已终止的线程 T2。
- 如果线程T1中断了线程T2,由T1中断 与任何其他线程(包括 T2)所在的任何点同步 确定 T2 已被中断(通过有一个 InterruptedException 抛出或通过调用 Thread.interrupted 或 Thread.isInterrupted).
如果没有这样的操作,您将看到其他线程中的对象处于未定义状态。
解决空指针异常的一种方法是使用 final 字段(参见 https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5)。但是你还必须同步 ArrayList。我建议使用 java.util.concurrent 包中的队列。