Firebase实时数据库中instanceof无法识别父类列表中子类对象
问题背景
你定义了如下继承结构的Java类:
// 父类 public class Parent{ public String title; public Parent(){} public void setTitle(String title){ this.title = title; } public String getTitle(){ return this.title; } } // 子类Child1 class Child1 extends Parent{ public String c1title; public Child1(){} public void setC1title(String title){ this.c1title = title; } public String getC1itle(){ return this.c1title; } // 注意:这里方法名拼写错误,应该是getC1title } // 子类Child2 class Child2 extends Parent{ public String c2title; public Child2(){} public void setC2title(String title){ this.c2title = title; } public String getC2itle(){ return this.c2title; } // 同样修正:getC2title }
你将Child1/Child2实例存入ArrayList<Parent>后上传到Firebase Realtime Database,能成功取回集合,但用instanceof判断子类时完全失效(总是进入else分支),而本地创建的集合却能正常工作。
核心原因
Firebase Realtime Database在序列化和反序列化对象时,不会保存对象的类型元信息。也就是说,当你把Child1实例作为Parent存入数据库时,Firebase只保存了对象的字段数据(title、c1title),不会记录这个对象原本是Child1类型。
当你用GenericTypeIndicator<ArrayList<Parent>>取回数据时,Firebase会直接创建Parent类的实例,把数据库里的字段值填充进去——这些对象本质上就是Parent类型,没有任何子类的类型标记,所以instanceof Child1或instanceof Child2自然会返回false。
而本地集合里的对象是你直接new Child1()/new Child2()创建的,JVM保留了完整的类型信息,所以instanceof能正常判断。
解决方案:手动添加类型标记
要解决这个问题,你需要在父类中添加一个类型标记字段,用来区分不同的子类。存入数据库时给每个子类实例设置对应的标记,取回时先根据标记判断类型,再反序列化为对应的子类对象。
步骤1:修改父类,添加类型标记
public class Parent{ public String title; public String type; // 新增:用来标记子类类型 public Parent(){} public void setTitle(String title){ this.title = title; } public String getTitle(){ return this.title; } // 新增type的getter/setter public void setType(String type){ this.type = type; } public String getType(){ return this.type; } }
步骤2:子类初始化时设置类型标记
class Child1 extends Parent{ public String c1title; public Child1(){ setType("child1"); // 固定标记为child1 } public void setC1title(String title){ this.c1title = title; } public String getC1title(){ return this.c1title; } // 修正拼写错误 } class Child2 extends Parent{ public String c2title; public Child2(){ setType("child2"); // 固定标记为child2 } public void setC2title(String title){ this.c2title = title; } public String getC2title(){ return this.c2title; } // 修正拼写错误 }
步骤3:取回数据时根据类型标记处理
推荐直接遍历每个子节点,根据type字段反序列化为对应的子类,这样比直接取ArrayList<Parent>更准确:
mDatabaseReference = FirebaseDatabase.getInstance().getReference("test"); mDatabaseReference.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { List<Parent> list = new ArrayList<>(); for(DataSnapshot itemSnapshot : dataSnapshot.getChildren()){ String type = itemSnapshot.child("type").getValue(String.class); if("child1".equals(type)){ // 反序列化为Child1 Child1 child1 = itemSnapshot.getValue(Child1.class); list.add(child1); Log.d("Activity1", "1st child"); } else if("child2".equals(type)){ // 反序列化为Child2 Child2 child2 = itemSnapshot.getValue(Child2.class); list.add(child2); Log.d("Activity1", "2nd child"); } else{ // 未知类型,作为Parent处理 Parent parent = itemSnapshot.getValue(Parent.class); list.add(parent); Log.d("Activity1", "No match found"); } } Log.d(TAG, "Successfully obtained the test list, size: " + list.size()); } @Override public void onCancelled(DatabaseError databaseError) { Log.e(TAG, "Database error occurred: " + databaseError.getMessage()); } });
额外提示
- 如果你坚持要直接取回
ArrayList<Parent>,也可以遍历集合中的每个Parent对象,根据type字段手动创建子类实例并赋值,但这种方式需要手动处理所有字段,不如上面的方法高效。 - 注意修正你代码中方法名的拼写错误(
getC1itle→getC1title,getC2itle→getC2title),避免后续出现空指针或逻辑错误。
内容的提问来源于stack exchange,提问作者forcehandler

