List 不能轉化為 List

雖然 String 是 Object 的子型別,但是 List<String> 不是 List<Object> 的子型別。如果 List<String> 是 List<Object> 的子型別的話,在面向抽象程式設計的時候,就可能向 String List 中加入其它型別的物件,顯然編譯不通過。
List<String> stringList = new ArrayList<String>; List<Object> objectList = stringList;// 編譯不通過 objectList.add(new XXXX()); // 加入其它型別 String s = stringList.get(0);//
今天遇到的一個問題就和這裡相關,沒能正確的理解這一點,導致呼叫了錯誤的方法。
問題抽象
定義一個支援泛型的模板類,其中有2個過載的方法,區別在於一個引數是單元素,另一個是集合。
public static class FooTemplate<K> { // foo 1 public void foo(K k) { System.out.println("foo : " + k); } // foo 2 public void foo(Collection<K> ks) { System.out.println("foos : " + ks.toString()); } }
出錯的場景是:
List<String> list = Arrays.asList("f1", "f2"); FooTemplate<Object> fooTemplate = new FooTemplate<>(); fooTemplate.foo(list);
這裡呼叫的方法是 foo 1,而不是我們想要的 foo 2。如果想要我們的 String List 呼叫 foo 2 方法,一種方法是明確泛型的具體型別為 String。
List<String> list = Arrays.asList("f1", "f2"); FooTemplate<String> fooTemplate1 = new FooTemplate<>(); fooTemplate1.foo(list);
另一種方法是把泛型去掉,其實Java中的泛型只是一種語法糖,編譯器層面的約束。
List<String> list = Arrays.asList("f1", "f2"); FooTemplate<Object> fooTemplate = new FooTemplate<>(); fooTemplate.foo((List)list);
接下來說明實際踩坑的地方。
使用 Redis 的時候
在刪除多個 redis key 的時候,把多個 key 放置在一個 String List 中,然後呼叫 RedisTemplate.delete 方法,使用的是 RedisTemplate<Object,Object> (因為 Spring Boot 的 Redis 自動配置需要這個型別)。然後會進入這個方法:
public void delete(K key) { final byte[] rawKey = rawKey(key); execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection connection) { connection.del(rawKey); return null; } }, true); }
而不是我們期待的這個方法:
public void delete(Collection<K> keys) { if (CollectionUtils.isEmpty(keys)) { return; } final byte[][] rawKeys = rawKeys(keys); execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection connection) { connection.del(rawKeys); return null; } }, true); }
導致出現 ClassCastException,如果使用 StringRedisTemplate 就不會遇到這個問題。
public class StringRedisTemplate extends RedisTemplate<String, String> { //...... }
