使用泛型与Firebase snapshot.getValue()的最佳实践

4

TL;DR:如何正确使用Firebase DataSnapshot.getValue()与通用类?

用例:我想使用Firebase实现一个单一的通用远程数据源类来处理所有实体(它们有很多)。当监听数据变化时,我想将datasnapshot的值作为类型为E的对象获取(其类型在其他地方确定),但我不知道是否可以使用Firebase查询来实现,如下所示:

public class GenRemoteDataSource<E extends SomeClass>
{
   //...
   public void onDataChange(DataSnapshot dataSnapshot)
   {
        E item = (E) dataSnapshot.getValue(); // <- unchecked cast and it doesn't work
        items.add(item);
   }
}

例如,我有一个扩展了SomeClass的Foo类,使用Foo类实现这个GenRemoteDataSource的方式如下:
public class Foo extends SomeClass{}

public class FooRemoteDataSource extends GenRemoteDataSource<Foo>
{
   //...
}

但是 Firebase 报了一个运行时错误,因为它会尝试将 getValue() 强制转换为 Foo,而实际上它会将 value 强制转换为上限 SomeClass。我很困惑为什么会发生这种情况:

Java.lang.ClassCastException: java.util.HashMap cannot be cast to com.example.app.SomeClass

请指导我如何实现类型安全(无未经检查的转换)。谢谢。
编辑:下面的东西被证明是不相关的,请参见GenericTypeIndicator 删除线: 编辑:我还尝试过(盲目尝试,但值得一试)GenericTypeIndicator,
GenericTypeIndicator<E> mTypeIndicator = new GenericTypeIndicator<>();
E item = dataSnapshot.getValue(mTypeIndicator);

但它却返回以下的运行时错误。
com.google.firebase.database.DatabaseException: Not a direct subclass of GenericTypeIndicator: class java.lang.Object

2个回答

2
TL;DR 我的解决方案,(引入了比语义上必要的依赖更多的依赖?)
public class GenRemoteDataSource<E extends SomeClass>
{
   //...
   private final Class<E> clazz;

   GenRemoteDataSource(Class<E> clazz)
   {
      this.clazz = clazz;
   }

   //...
   public void onDataChange(DataSnapshot dataSnapshot)
   {
        E item = dataSnapshot.getValue(clazz); // <- now is type safe.
        items.add(item);
   }
}

最终,我只是通过构造函数注入将类定义提供给了getValue()。这是通过将Class<T>传递到泛型类的构造函数中,并将其设置为Firebase的getValue()所使用的实例参数来完成的。如果有更好的解决方案,请评论,因为对我来说这似乎是多余的。谢谢!


1

对于任何有兴趣的人,这是我在 Kotlin 中的解决方案:


class FirebaseParser<T>(val klass: Class<T>) {

    companion object {
        inline operator fun <reified T : Any> invoke() = FirebaseParser(T::class.java)
    }

    private fun checkType(t: Any): Boolean {
        return when {
            klass.isAssignableFrom(t.javaClass) -> true
            else -> false
        }

    }

    fun convert(data: DataSnapshot): T {

            val res = data.getValue() // Get out whatever is there.
                ?: throw MyCustomExceptionWhichIsHandledElseWhere("Data was null") // If its null throw exception

            if(checkType(res)) {
                return res as T //typecast is now safe
            } else {
                throw MyCustomExceptionWhichIsHandledElseWhere("Data was of wrong type. Expected: " + klass  + " but got: " + res.javaClass) // Data was the wrong type throw exception
            }
    }

}

实例化的示例

以下是从Java实例化类的一些示例:

FirebaseParser<String> firebaseStringParser = new FirebaseParser<>(String.class);
FirebaseParser<Boolean> firebaseBooleanParser = new FirebaseParser<>(Boolean.class);


以及来自Kotlin

val stringParser: FirebaseParser<String> = FirebaseParser(String::class.java)
val booleanParser: FirebaseParser<Boolean> = FirebaseParser(Boolean::class.javaObjectType) //javaObjectType so we do not compare Boolean to boolean without knowing

如果在 Kotlin 中这样做,你需要确保在不知道的情况下不要将原始类型与类类型进行比较。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接