Android中读取int变量时出现NullPointerException的原因及修复方案
嘿,我来帮你搞定这个烦人的空指针异常问题!先理清楚你的场景:你设置了两个自定义时间选择器,点击按钮3想获取它们的时间值时触发了NullPointerException,报错日志显示你在访问一个null对象的hourOfDay字段。
问题还原
先把你的代码和报错信息整理出来,方便分析:
报错日志
java.lang.NullPointerException: Attempt to read from field 'int com.ahmedco.testcode2.TimePickerDialog.hourOfDay' on a null object reference at com.ahmedco.testcode2.MainActivity$3.onClick(MainActivity.java:65) at android.view.View.performClick(View.java:6256) at android.view.View$PerformClick.run(View.java:24701) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6541) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
MainActivity代码
public class MainActivity extends AppCompatActivity { Button b1, b2, b3; DialogFragment timePickerDialog1, timePickerDialog2; int hourTimer1, hourTimer2, minuteTimer1, minuteTimer2; String time1Format, time2Format, AM_PMTimer1, AM_PMTimer2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); b1 = (Button) findViewById(R.id.button); b2 = (Button) findViewById(R.id.button2); b3 = (Button) findViewById(R.id.button3); b1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { timePickerDialog1 = new TimePickerDialog(1); timePickerDialog1.show(getSupportFragmentManager(), "timePicker"); } }); b2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { timePickerDialog2 = new TimePickerDialog(2); timePickerDialog2.show(getSupportFragmentManager(), "timePicker"); } }); b3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { hourTimer1 = ((TimePickerDialog) timePickerDialog1).hourOfDay; hourTimer2 = ((TimePickerDialog) timePickerDialog2).hourOfDay; minuteTimer1 = ((TimePickerDialog) timePickerDialog1).minute; minuteTimer2 = ((TimePickerDialog) timePickerDialog2).minute; AM_PMTimer1 = ((TimePickerDialog) timePickerDialog1).AM_PM; AM_PMTimer2 = ((TimePickerDialog) timePickerDialog2).AM_PM; time1Format = "" + hourTimer1 + ":" + minuteTimer1 + " " + AM_PMTimer1; time2Format = "" + hourTimer1 + ":" + minuteTimer1 + " " + AM_PMTimer2; Log.i("time1Format", "" + time1Format); Log.i("time2Format", "" + time2Format); } }); } }
TimePickerDialog代码
public class TimePickerDialog extends DialogFragment implements android.app.TimePickerDialog.OnTimeSetListener { public String AM_PM = ""; public int hourOfDay = 0; public int minute = 0; public TimePickerDialog(int id) { // currentEditText = id; } @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { final Calendar c = Calendar.getInstance(); int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); return new android.app.TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity())); } @Override public void onTimeSet(TimePicker view, int hourOfDay_, int minute_) { if (hourOfDay_ > 12) { AM_PM = "PM"; hourOfDay_ = hourOfDay_ - 12; } else { AM_PM = "AM"; } hourOfDay = hourOfDay_; minute = minute_; } }
问题根源
- 空对象直接访问:如果用户没点击按钮1或2,
timePickerDialog1/timePickerDialog2会保持初始的null状态,此时强制转换并访问字段必然触发空指针。 - Fragment生命周期坑:就算用户之前打开过时间选择器,对话框关闭后Fragment可能被系统销毁,你的引用会变成无效的空对象,再访问字段也会出错。
- 小bug:你拼接
time2Format时写错了变量,用了hourTimer1和minuteTimer1,应该对应第二个时间的变量。
修复方案
给你两个方案,第一个快速解决,第二个是Android推荐的最佳实践,更健壮:
方案1:快速修复——添加空值检查
在按钮3的点击事件里先判断对话框实例是否存在,不存在就提示用户先选择时间:
b3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 检查两个时间选择器是否都已初始化 if (timePickerDialog1 == null || timePickerDialog2 == null) { Toast.makeText(MainActivity.this, "请先选择两个时间哦!", Toast.LENGTH_SHORT).show(); return; } // 提前强转,避免重复转换 TimePickerDialog dialog1 = (TimePickerDialog) timePickerDialog1; TimePickerDialog dialog2 = (TimePickerDialog) timePickerDialog2; hourTimer1 = dialog1.hourOfDay; hourTimer2 = dialog2.hourOfDay; minuteTimer1 = dialog1.minute; minuteTimer2 = dialog2.minute; AM_PMTimer1 = dialog1.AM_PM; AM_PMTimer2 = dialog2.AM_PM; // 修复time2Format的变量错误,用String.format更简洁 time1Format = String.format("%d:%d %s", hourTimer1, minuteTimer1, AM_PMTimer1); time2Format = String.format("%d:%d %s", hourTimer2, minuteTimer2, AM_PMTimer2); Log.i("time1Format", time1Format); Log.i("time2Format", time2Format); } });
方案2:最佳实践——用回调传递数据(彻底避免空指针)
直接访问Fragment的公共字段不是好设计,我们用回调接口让时间选择器主动把数据传给MainActivity,完全不用依赖Fragment实例:
第一步:修改TimePickerDialog,添加回调接口
public class TimePickerDialog extends DialogFragment implements android.app.TimePickerDialog.OnTimeSetListener { // 定义回调接口,用于传递时间数据 public interface OnTimeSelectedListener { void onTimeSelected(int dialogId, int hour, int minute, String amPm); } private OnTimeSelectedListener listener; private int dialogId; // 标记是第一个还是第二个时间选择器 public TimePickerDialog(int id) { this.dialogId = id; } @Override public void onAttach(@NonNull Context context) { super.onAttach(context); // 确保宿主Activity实现了回调接口 try { listener = (OnTimeSelectedListener) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() + " 必须实现OnTimeSelectedListener接口!"); } } @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { final Calendar c = Calendar.getInstance(); int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); return new android.app.TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity())); } @Override public void onTimeSet(TimePicker view, int hourOfDay_, int minute_) { String amPm; int hour = hourOfDay_; if (hourOfDay_ > 12) { amPm = "PM"; hour = hourOfDay_ - 12; } else { amPm = "AM"; } // 选择时间后,通过回调把数据传给MainActivity if (listener != null) { listener.onTimeSelected(dialogId, hour, minute_, amPm); } } }
第二步:修改MainActivity,实现回调接口
public class MainActivity extends AppCompatActivity implements TimePickerDialog.OnTimeSelectedListener { Button b1, b2, b3; // 直接存储时间数据,无需依赖Fragment实例 int hourTimer1 = -1, hourTimer2 = -1, minuteTimer1 = -1, minuteTimer2 = -1; String AM_PMTimer1 = "", AM_PMTimer2 = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); b1 = findViewById(R.id.button); b2 = findViewById(R.id.button2); b3 = findViewById(R.id.button3); b1.setOnClickListener(view -> { new TimePickerDialog(1).show(getSupportFragmentManager(), "timePicker1"); }); b2.setOnClickListener(view -> { new TimePickerDialog(2).show(getSupportFragmentManager(), "timePicker2"); }); b3.setOnClickListener(view -> { // 检查是否已选择两个时间 if (hourTimer1 == -1 || hourTimer2 == -1) { Toast.makeText(this, "请先选择两个时间哦!", Toast.LENGTH_SHORT).show(); return; } String time1Format = String.format("%d:%d %s", hourTimer1, minuteTimer1, AM_PMTimer1); String time2Format = String.format("%d:%d %s", hourTimer2, minuteTimer2, AM_PMTimer2); Log.i("time1Format", time1Format); Log.i("time2Format", time2Format); }); } // 实现回调方法,接收时间选择器传来的数据 @Override public void onTimeSelected(int dialogId, int hour, int minute, String amPm) { if (dialogId == 1) { hourTimer1 = hour; minuteTimer1 = minute; AM_PMTimer1 = amPm; } else if (dialogId == 2) { hourTimer2 = hour; minuteTimer2 = minute; AM_PMTimer2 = amPm; } } }
额外说明
- 方案2不仅解决了空指针问题,还符合Android组件间通信的最佳实践,Fragment不用暴露内部字段,代码更解耦、更健壮。
- 我已经帮你修复了
time2Format的拼接错误,现在会正确使用第二个时间的变量。
内容的提问来源于stack exchange,提问作者hosam hassn

