为什么在构造函数中抛出异常导致空引用? 例如,如果我们运行下面的代码,则teacher的值为null,而st.teacher则不是(创建了Teacher对象)。为什么呢?
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main( string[] args )
{
Test();
}
private static void Test()
{
Teacher teacher = null;
Student st = new Student();
try
{
teacher = new Teacher( "", st );
}
catch ( Exception e )
{
Console.WriteLine( e.Message );
}
Console.WriteLine( ( teacher == null ) ); // output True
Console.WriteLine( ( st.teacher == null ) ); // output False
}
}
class Teacher
{
public string name;
public Teacher( string name, Student student )
{
student.teacher = this;
if ( name.Length < 5 )
throw new ArgumentException( "Name must be at least 5 characters long." );
}
}
class Student
{
public Teacher teacher;
}
}
答案 0 :(得分:37)
构造函数永远不会完成,因此永远不会发生赋值。它不是从构造函数返回null(或者有一个“null对象” - 没有这样的概念)。只是你永远不会为teacher
分配新值,所以它保留了以前的值。
例如,如果您使用:
Teacher teacher = new Teacher("This is valid", new Student());
Student st = new Student();
try
{
teacher = new Teacher("", st);
}
catch (... etc ...)
...那么你仍然会有“这是有效的”老师。 name
变量仍然不会在Teacher
对象中分配值,因为您的Teacher
构造函数缺少一行,例如:
this.name = name;
答案 1 :(得分:13)
因为您正在检查参考。
try
{
teacher = new Teacher( "", st ); //this line raises an exception
// so teacher REMAINS NULL.
// it's NOT ASSIGNED to NULL,
// but just NOT initialized. That is.
}
catch ( Exception e )
{
Console.WriteLine( e.Message );
}
但是
public Teacher( string name, Student student )
{
student.teacher = this; //st.Teacher is assigned BEFORE exception raised.
if ( name.Length < 5 )
throw new ArgumentException( "Name must be at least 5 characters long." );
}
答案 2 :(得分:3)
在构造函数中抛出异常时,会破坏对象的构造。所以它永远不会完成,因此,没有任何回报的对象。事实上,从未执行该赋值运算符(teacher = new Teacher( "", st );
),因为异常会中断调用堆栈。
教师构造函数仍然将对自身(正在构造的对象)的引用写入Student对象的属性。但是之后你永远不应该尝试使用这个教师对象,因为它尚未构建。这可能会导致未定义的行为。
答案 3 :(得分:1)
如果Foo
是引用类型,则语句Foo = new FooType();
将构造一个对象,然后,在构造函数完成之后,将引用存储到Foo
。如果构造函数抛出异常,则在没有编写Foo
的情况下,将跳过将引用存储到Foo
的代码。
如果:
try
/ catch
块Foo
的情况下发表声明。Foo
在catch
块周围的上下文中定义的局部变量。Foo
之后没有写入catch
的情况下到达Foo
的语句。编译器将假设后者尝试读取Foo
可以在没有编写Foo
的情况下执行,并且在这种情况下将拒绝编译。编译器将允许在未编写的情况下读取Foo
,但是,如果:
Foo
是一个类字段,或存储在类字段中的结构字段,存储在类字段中存储的结构字段中的结构字段等。out
作为foo
参数传递给一个方法(用C#以外的语言编写),该方法不存储任何内容,并且读取Foo
的语句将只有在方法正常返回而不是通过异常返回时才可以访问。在前一种情况下,null
的定义值为Foo
。在后一种情况下,null
的值在执行方法时第一次创建时可能为null;如果在循环内重新创建,它可能包含FooType
或在最后一次创建后写入的最后一个值;该标准并未具体说明在这种情况下会发生什么。
请注意,如果Foo = new FooType();
有类似普通构造函数的内容,Foo
将永远导致 Foo
变为空,如果不是之前的话。如果语句正常完成,FooType
将保存对精确类型Foo
的实例的引用,该实例之前在Universe中的任何位置都没有引用;如果它抛出异常,它不会以任何方式影响private static final int TERMIN_LOADER_ID = 1;
private static final int AUFGABEN_LOADER_ID = 2;
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
switch(i){
case TERMIN_LOADER_ID:
return new TerminLoader(getActivity());
case AUFGABEN_LOADER_ID:
throw new UnsupportedOperationException("AUFGABEN_LOADER_ID not implemented yet.");
default:
throw new UnsupportedOperationException("Unknown loader id.");
}
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
// mAdapter is a CursorAdapter
//mAdapter.swapCursor(cursor);
switch(cursorLoader.getId()){
case TERMIN_LOADER_ID:
RadListView termineListView = (RadListView)getActivity().findViewById(R.id.termineRadListView);
// now here we can fill our termineListView because we got the cursor ready
ArrayList<Termin> termine = new ArrayList<>();
cursor.moveToFirst();
while(!cursor.isAfterLast()){
// loop over the results from the query and build our termine ArrayList
// this is our MatrixCursor from our TerminLoader
long terminId = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events._ID));
String title = cursor.getString(cursor.getColumnIndex(CalendarContract.Events.TITLE));
long begin = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events.DTSTART));
Termin termin = new Termin(terminId, title, begin);
termine.add(termin);
cursor.moveToNext();
}
TerminListViewAdapter terminListViewAdapter = new TerminListViewAdapter(termine, getActivity());
termineListView.setAdapter(terminListViewAdapter);
break;
case AUFGABEN_LOADER_ID:
throw new UnsupportedOperationException("AUFGABEN_LOADER_ID not implemented yet.");
default:
throw new UnsupportedOperationException("Unknown LOADER_ID.");
}
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
switch(cursorLoader.getId()){
case TERMIN_LOADER_ID:
// now here we can fill our termineListView because we got the cursor ready
//RadListView termineListView = (RadListView)getActivity().findViewById(R.id.termineRadListView);
ArrayList<Termin> termine = new ArrayList<>();
TerminListViewAdapter terminListViewAdapter = new TerminListViewAdapter(termine, getActivity());
this.termineListView.setAdapter(terminListViewAdapter);
break;
case AUFGABEN_LOADER_ID:
throw new UnsupportedOperationException("AUFGABEN_LOADER_ID not implemented yet.");
default:
throw new UnsupportedOperationException("Unknown LOADER_ID.");
}
}
@Override
public void onStart() {
super.onStart();
loadData();
}
public void loadData(){
// reload termine
getActivity().getSupportLoaderManager().initLoader(TERMIN_LOADER_ID, null, this);
}
。
答案 4 :(得分:0)
您在分配后抛出异常 'student.teacher = this; //这一行被执行了 if(name.Length&lt; 5)//选中此项并在指定的情况下为true 抛出新的ArgumentException(“名称长度必须至少为5个字符。”); // BAM:此处抛出异常。
因此,teacher的值为null(在构造函数完成之前抛出的异常),而st.teacher则不是!
答案 5 :(得分:-1)
构造函数的主要工作是初始化对象。如果在初始化本身中存在异常,那么没有一个点没有正确初始化的对象。 因此,从构造函数中抛出异常会导致null对象。