Java 泛型 - 在我调用 instanceof 之后,有什么方法可以避免强制转换(和未经检查的警告)?

标签 java android generics

Android 代码 - SharedPreferences 类导出不同的方法来保存/检索不同的首选项:

@SuppressWarnings("unchecked")
public static <T> T retrieve(Context ctx, String key, T defaultValue) {
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
    if (defaultValue instanceof Boolean) return (T) (Boolean) prefs
            .getBoolean(key, (Boolean) defaultValue);
    else if (defaultValue instanceof Float) return (T) (Float) prefs
            .getFloat(key, (Float) defaultValue);
    // etc - another 4 cases
}

这行得通,我可以调用boolean stored = retrieve(ctx, "BOOLEAN_KEY", true)好的 - 但我的问题是:因为我已经使用了 instanceofT归结为一个特定的类有没有办法避免单一和双重类型转换和warning : unchecked ?

编辑 : 如果我通过了这门课,我不妨调用 getBoolean()getFloat()等等。我想要的是简化方法的内部结构并消除警告,但仍然可以调用 retrieve(ctx, "KEY", 1 or "string" or true)并得到我想要的东西

最佳答案

简短的回答:不,您无法摆脱警告。他们在那里是有原因的。

更长的答案:您可能知道,Java 中的泛型只是语法糖加上编译时检查;几乎没有任何东西可以保存到运行时(一个称为“删除”的过程)。这意味着在您的方法中对 (T) 的强制转换实际上是无操作的。它将变成最具体的类型,在这种情况下是 Object 。所以这:

(T) (Boolean) prefs.whatever()

真的变成了这样:
(Object) (Boolean) prefs.whatever()

这当然和刚才一样:
(Boolean) prefs.whatever()

这会让你陷入危险的境地,这正是警告试图告诉你的。基本上,您正在失去类型安全性,它最终可能会让您远离错误的实际位置(因此难以追踪)。想象一下:
// wherever you see "T" here, think "Object" due to erasure
public <T> void prefsToMap(String key, T defaultValue, Map<String, T> map) {
    T val = retrieve(this.context, key, defaultValue);
    map.put(key, val);
}

Map<String,Integer> map = new HashMap<>();
prefsToMap("foo", 123, map);
// ... later
Integer val = map.get("foo");

到目前为止一切顺利,在您的情况下它会起作用,因为如果“foo”在首选项中,您将调用 getInt 来获取它。但是想象一下,如果您的 retrieve 函数中存在错误,使得 if( defaultValue instanceof Integer) 意外返回 getDouble() 而不是 getInt()(带有强制转换和所有这些)。编译器不会捕获它,因为您对 T 的强制转换实际上只是对 Object 的强制转换,这始终是允许的!直到 Integer val = map.get("foo"); 变成:
Integer val = (Integer) map.get("foo"); // cast automatically inserted by the compiler

这个 Actor 可能离错误真正发生的地方很远 - getObject 调用 - 使得很难追踪。 Javac 试图保护您免受这种情况的影响。

这是一个将所有内容放在一起的示例。在这个例子中,我将使用 Number 而不是 prefs 对象,只是为了简单起见。您可以复制粘贴此示例并按原样试用。
import java.util.*;

public class Test {
    @SuppressWarnings("unchecked")
    public static <T> T getNumber(Number num, T defaultVal) {
        if (num == null)
            return defaultVal;
        if (defaultVal instanceof Integer)
            return (T) (Integer) num.intValue();
        if (defaultVal instanceof String)
            return (T) num.toString();
        if (defaultVal instanceof Long)
            return (T) (Double) num.doubleValue(); // oops!
        throw new AssertionError(defaultVal.getClass());
    }

    public static void getInt() {
        int val = getNumber(null, 1);
    }

    public static void getLong() {
        long val = getNumber(123, 456L); // This would cause a ClassCastException
    }

    public static <T> void prefsToMap(Number num, String key, T defaultValue, Map<String, T> map) {
        T val = getNumber(num, defaultValue);
        map.put(key, val);
    }

    public static void main(String[] args) {
        Map<String, Long> map = new HashMap<String,Long>();
        Long oneTwoThree = 123L;
        Long fourFixSix = 456L;
        prefsToMap(oneTwoThree, "foo", fourFixSix, map);
        System.out.println(map);
        Long fromMap = map.get("foo"); // Boom! ClassCastException
        System.out.println(fromMap);
    }
}

需要注意的几点:
  • 大的: 尽管泛型应该给我类型安全,但我得到了 ClassCastException。不仅如此,我在一段完全没有错误的代码中得到了错误( main )。错误发生在 prefsToMap 中,但 main 支付了费用。如果 map 是一个实例变量,则可能很难跟踪该错误是在哪里引入的。
  • 除了使用数字而不是首选项之外,我的 getNumber 与您的 retrieve 函数几乎相同
  • 我故意制造了一个错误:如果 defaultVal 是一个 Long ,我得到(并转换为 T )一个 double 而不是长精度。但是类型系统无法捕获这个错误,这正是未经检查的 Actor 试图警告我的(它警告我它无法捕获任何错误,而不是一定有错误)。
  • 如果defaultValue 是int 或String,一切都会好起来的。但是,如果它是 Long,并且 num 为空,那么当调用站点需要 Double 时,我将返回一个 Long
  • 因为我的 prefsToMap 类只转换为 T - 如上所述,这是一个无操作转换 - 它不会导致任何转换异常。直到倒数第二行 Long fromMap = map.get("foo") ,我才得到异常。

  • 使用 javap -c ,我们可以看到其中一些在字节码中的样子。首先,让我们看一下 getNumber 。请注意,对 T 的强制转换不会显示为任何内容:
    public static java.lang.Object getNumber(java.lang.Number, java.lang.Object);
      Code:
       0:   aload_0
       1:   ifnonnull   6
       4:   aload_1
       5:   areturn
       6:   aload_1
       7:   instanceof  #2; //class java/lang/Integer
       10:  ifeq    21
       13:  aload_0
       14:  invokevirtual   #3; //Method java/lang/Number.intValue:()I
       17:  invokestatic    #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       20:  areturn
       21:  aload_1
       22:  instanceof  #5; //class java/lang/String
       25:  ifeq    33
       28:  aload_0
       29:  invokevirtual   #6; //Method java/lang/Object.toString:()Ljava/lang/String;
       32:  areturn
       33:  aload_1
       34:  instanceof  #7; //class java/lang/Long
       37:  ifeq    48
       40:  aload_0
       41:  invokevirtual   #8; //Method java/lang/Number.doubleValue:()D
       44:  invokestatic    #9; //Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
       47:  areturn
       48:  new #10; //class java/lang/AssertionError
       51:  dup
       52:  aload_1
       53:  invokevirtual   #11; //Method java/lang/Object.getClass:()Ljava/lang/Class;
       56:  invokespecial   #12; //Method java/lang/AssertionError."<init>":(Ljava/lang/Object;)V
       59:  athrow
    

    接下来,看看 getLong 。请注意,它将 getNumber 的结果转换为 Long :
    public static void getLong();
      Code:
       0:   bipush  123
       2:   invokestatic    #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5:   ldc2_w  #15; //long 456l
       8:   invokestatic    #17; //Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
       11:  invokestatic    #13; //Method getNumber:(Ljava/lang/Number;Ljava/lang/Object;)Ljava/lang/Object;
       14:  checkcast   #7; //class java/lang/Long
       17:  invokevirtual   #18; //Method java/lang/Long.longValue:()J
       20:  lstore_0
       21:  return
    

    最后,这里是 prefsToMap 。请注意,因为它只处理通用的 T 类型——也就是 Object——它根本不做任何转换。
    public static void prefsToMap(java.lang.Number, java.lang.String, java.lang.Object, java.util.Map);
      Code:
       0:   aload_0
       1:   aload_2
       2:   invokestatic    #13; //Method getNumber:(Ljava/lang/Number;Ljava/lang/Object;)Ljava/lang/Object;
       5:   astore  4
       7:   aload_3
       8:   aload_1
       9:   aload   4
       11:  invokeinterface #19,  3; //InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
       16:  pop
       17:  return
    

    关于Java 泛型 - 在我调用 instanceof 之后,有什么方法可以避免强制转换(和未经检查的警告)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16304609/

    相关文章:

    java - retrofit 2 : Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

    java - 如何将上下文作为参数传递

    java - 错误 :Execution failed for task ':app:transformClassesWithFirebasePerformancePluginForRelease'

    Java Generics调用self类解释

    .net - IMediatR - 通用请求的通用请求处理程序

    c# - 反射以查找实现具有多个开放通用参数的接口(interface)的类

    java - Servlet RequestDispatcher 不转发

    java - BufferedReader 无法读取日志文件中的新行

    java - 修改IntelliJ中Java代码编辑器生成 `toString`方法使用的模板

    java - java中使用dao类获取行的异常