簡體   English   中英

Java泛型-在調用instanceof之后,有什么方法可以避免強制轉換(和未經檢查的警告)?

[英]Java generics - any way to avoid casts (and unchecked warnings) after I have called instanceof?

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已簡化為特定的類,因此有一種避免單boolean stored = retrieve(ctx, "BOOLEAN_KEY", true)的方法演員和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 = map.get("foo"); ,它變成:

Integer val = (Integer) map.get("foo"); // cast automatically inserted by the compiler

此強制轉換可能與錯誤真正發生的地方( 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函數幾乎相同
  • 我故意創建了一個錯誤:如果defaultValLong ,我將得到double(而不是long)(並將其強制轉換為T )。 但是類型系統無法捕獲此錯誤,這正是未經檢查的強制轉換試圖警告我的內容(警告我它不能捕獲任何錯誤,不一定有錯誤)。
  • 如果defaultValue是int或String,則一切都會好起來。 但是,如果它是Long,並且num為null,那么當呼叫站點期望Long時,我將返回Double
  • 因為我的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類型(也稱為對象),因此根本不進行任何轉換。

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

通常的方法是使用Class.cast(obj),但是您將需要一個T類的實例,通常通過將一個實例傳遞給該方法來完成,但是您的情況會很好:

return Boolean.class.cast(pregs.getBoolean(key, (Boolean)defaultValue));

編輯:評論后,是的,可能是類型不匹配的問題。

您將需要將類類型作為方法的一部分傳遞,或者從默認值(如果它不為null)進行推斷:

return defaultValue.getClass().cast(pregs.getBoolean(key, (Boolean)defaultValue));

使用工作示例再次進行編輯(這對我沒有任何警告):

public class JunkA {

    private boolean getBoolean(String key, boolean def) {
        return def;
    }

    private float getFloat(String key, float def) {
        return def;
    }

    private String getString(String key, String def) {
        return def;
    }

    private int getInt(String key, int def) {
        return def;
    }

    public <T> T getProperty(final Class<T> clazz, final String key,
            final T defval) {
        if (clazz.isAssignableFrom(Boolean.class)) {
            return clazz.cast(getBoolean(key, (Boolean) defval));
        }
        if (clazz.isAssignableFrom(String.class)) {
            return clazz.cast(getString(key, (String) defval));
        }
        if (clazz.isAssignableFrom(Boolean.class)) {
            return clazz.cast(getFloat(key, (Float) defval));
        }
        if (clazz.isAssignableFrom(Integer.class)) {
            return clazz.cast(getInt(key, (Integer) defval));
        }
        return defval;
    }

}

好吧我的問題 是(簡單): 由於我已經使用過instanceof,並且T歸結為特定的類,有沒有一種方法可以避免單次和兩次強制轉換以及警告:未選中?

答案是否定的 -我引用此特定答案是因為它表明我並不是唯一一個納悶的人。 但是您可能希望對這個有趣的問題進行投票,盡管@yshavit的主題答案有些偏離:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM