【问题标题】:Best practice for using generics with Firebase snapshot.getValue()将泛型与 Firebase snapshot.getValue() 结合使用的最佳实践
【发布时间】:2018-06-22 14:18:36
【问题描述】:

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 类,这个 GenRemoteDataSource 与 Foo 类的实现将是:

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

请告知我应该如何使用 Type-safety (No unchecked cast) 来做到这一点。谢谢。

编辑下面的东西被证明是无关紧要的,见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

【问题讨论】:

    标签: java generics firebase-realtime-database


    【解决方案1】:

    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&lt;T&gt; 传递到泛型类的构造函数并将其设置为 Firebase 的 getValue() 以用作实例参数来完成的。如果有更好的解决方案,请发表评论,因为对我来说这似乎是多余的。谢谢!

    【讨论】:

      【解决方案2】:

      我在 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 中这样做,您需要确保不会在不知道的情况下将原始类型与类类型进行比较

      【讨论】:

        猜你喜欢
        • 2010-09-18
        • 2013-08-20
        • 2010-11-28
        • 2010-10-29
        • 2010-09-06
        • 1970-01-01
        • 2016-09-21
        • 2013-08-11
        • 2014-12-13
        相关资源
        最近更新 更多