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

2023-11-29

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)好吧 - 但我的问题是:因为我已经使用过instanceof and T归结为一个特定的类,有没有办法避免单次和双次强制转换以及warning : unchecked ?

EDIT


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

更长的答案:正如你所知,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

这个强制转换可能离错误真正发生的地方很远——getObject打电话——很难追踪。 Javac 正在努力保护您免受这种情况的影响。

这是一个将所有内容放在一起的示例。在此示例中,我将使用Number而不是首选项对象,只是为了简单起见。您可以复制粘贴此示例并按原样尝试。

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, but main支付了费用。如果map是一个实例变量,它可以是very很难跟踪该错误是在哪里引入的。
  • 除了使用数字而不是首选项之外,我的getNumber和你的几乎一样retrieve功能
  • 我故意制造了一个错误:如果defaultVal is a Long,我得到(并投射到T) a double而不是长。但是类型系统无法捕获此错误,这正是未经检查的强制转换试图警告我的内容(它警告我它无法捕获任何错误,而不是一定存在错误)。
  • If defaultValue是 int 或 String,一切都会好起来的。但如果它是一个 Long,并且num为空,那么我将返回一个Double例如,当调用站点期望Long.
  • 因为我的prefsToMap类仅强制转换为T-- 如上所述,这是一个无操作强制转换 -- 它不会导致任何强制转换异常。直到倒数第二行我才得到异常,Long fromMap = map.get("foo").

Using 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 to a 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。请注意,因为它只处理通用的Ttype——又名对象——它根本不做任何转换。

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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 基于磁盘的 HashMap [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 Java 是否有 或者是否有可用的库 允许我拥有基于磁盘的 HashMap 它不需要是原子的或任何东西
  • 将对象传递给活动

    我可以在第一个活动中初始化对象并在所有活动中初始化对象吗 public class Calc int x int y public Calc int x int y this x x this y y public int sum retu
  • 在 Android 模拟器上运行 Google 地图 v2

    我尝试在我的 Android 应用程序中实现 Google 地图 v2 但不幸的是 我收到的不是地图 而是以下消息 是否可以在 Android 模拟器 平台 4 2 上运行这些地图 目前 引用 Google Android Map API
  • 错误包括 bouncycastle 提供商

    我需要使用bouncycastle provider我的项目中的库 我已将其包含在 gradle 项目中 apply plugin application sourceCompatibility 1 6 version 1 0 0 main
  • RecyclerView onClick notificationItemRemoved 不会触发 onBindView

    I use notifyItemRemoved 方法我想更改其他剩余项目 但该方法没有触发onBindView 方法 除了使用notifyDataSetChanged 我想要附带的动画notifyItemRemoved method 如果您
  • AndroidPlot:从 1 到 11 的域标签

    我已经在我的应用程序中实现了 AndroidPlot 除了 X 轴标签 从 0 到 10 之外 它工作得很好 我想显示 1 到 11 此外 Y 轴上的标签不会出现 我正在使用的代码 import java text DecimalForma
  • 如何更改Android软键盘中任意键的按键背景

    我想让键盘上的一些键与其他键不同 例如下图中的shift 删除 空格键 根据google的参考文档 我们可以通过使用 来改变按键的背景android keybackground drawable xxx in input xml 但它改变了
  • Java 日期和 MySQL 时间戳时区

    我正在编辑一段代码 其基本功能是 timestamp new Date 然后坚持下去timestamp中的变量TIMESTAMPMySQL 表列 然而 通过调试我看到Date显示在正确时区的对象 GMT 1 当持久化在数据库上时 它是GMT
  • 如何在没有片段的情况下使用导航抽屉?

    我正在尝试遵循这个tutorial https developer android com training implementing navigation nav drawer html关于如何创建导航抽屉 但我不想在用户从抽屉列表中选择
  • 如何让 Camel FTP 按需只获取一次

    我对骆驼还很陌生 我一直在尝试让 Camel 根据需要仅通过 FTP 获取单个文件一次 我无法让它发挥作用 这是我尝试过的 让我知道什么是最好的方法以及我的代码有什么问题 1 读取文件后发送一条空消息当收到空消息时 停止路由 from di
  • 如何在flutter app android中添加Startapp广告?

    我想用其他广告更改 AdMob 广告 一些个人问题 如何在flutter app android中添加Startapp广告 有什么方法可以将启动广告添加到我的 flutter 应用程序 android 中 StartApp 现已更名为 St
  • java.lang.IllegalStateException:FragmentManager 已被销毁

    活动中onResume我称之为 volley request 的方法 它获取项目列表 然后将它们加载到此活动内的 ListFragment 中 当我第一次进入活动时 一切正常 但当我重新进入活动时 ListFragment 为空 并且控制台
  • Android动态功能模块,找不到资源

    当下载的功能模块发布到 Play 商店时 我在启动活动时遇到问题 它总是在下载模块活动中的 setContentView 上崩溃 java lang RuntimeException Unable to start activity Com
  • 访问手机内部存储以推送 SQLite 数据库文件

    我正在使用 Netbeans 和 java 开发我的 Android 应用程序 当我使用模拟器时 我可以访问文件资源管理器并通过访问以下路径将 SQLite 数据库插入到设备内存中 data data com example helloan
  • 原子整数的compareandexchange()与compareandset()

    在研究 AtomicInteger 时 我发现这个 API 提供了两种方法 比较和交换 如果当前值被引用 则自动将该值设置为 newValue to 作为见证值 预期值 记忆效应为 由指定VarHandle compareAndExchan
  • 优雅地避免 Java 中的 NullPointerException

    考虑这一行 if object getAttribute someAttr equals true 显然这一行是一个潜在的错误 属性可能是null我们会得到一个NullPointerException 因此我们需要将其重构为以下两个选择之一
  • 当框架被拖动时,如何设置 JWindow 的位置位于文本字段下方?

    我正在制作一个自动完成项目 就像谷歌一样 我的框架中有一个 jtextfield 每当我在该字段中输入内容时 该文本字段下方就会出现一个 JWindow 并且该窗口来自另一个类 现在的问题是 每当我拖动框架时 如何使窗口始终出现在文本字段下
  • 如何从 Android 应用程序调用 REST API? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我是 android 新手 也是编程新手 如何从 Android 应用程序调用 REST api GET POST 请求 请给我推荐一
  • SWT StyledText 有高度限制吗?

    我正在尝试创建一个应用程序 其中包含在 ScrolledComposite 中显示的 StyledText 框 我在 StyledText 框中显示大量行时遇到困难 超过 2 550 行似乎会导致问题 StyledText 框本身不能有滚动
  • Spring Boot 2 中的 401 代替 403

    With 春季启动 https projects spring io spring boot 1 5 6 发布我能够发送 HTTP 状态代码401代替403如中所述如果请求未经身份验证的uri 如何让Spring Security响应未经授

随机推荐