如上, 第一个参数为 String 类型, 第二个是 List 类型, 貌似两个 T 是不匹配的, 但是这里编译器会自动将 String 转换为 Object 类型, 并将 T 标识为 Object . 继承关系的泛型参数 定义如下类: public class A { }
public class B extends A { }
public class C extends A { } 新建并初始化如下类对象: List<A> listA = new ArrayList<A>();
List<B> listB = new ArrayList<B>(); 因为 A 是 B 和 C 类的共同父类, 那么 List 是不是 List 和 List 的共同父类吗? 换句话说, 是否可以将 List 引用指向 List 对象呢? 或者反过来让 List 引用指向 List 对象呢? 答案是不可以, 第一种情况, 父类引用指向子类对象, 如将 List 引用指向 List 对象, 先假设这是可以的, 有如下代码: List<A> listA = listB; 这会出现问题, 因为 listA 是 List 类型, 它可以放入任何 A 对象作为元素, 可以放入 B 对象, 也可以放入 C 对象, 但是它实际指向的是一个 List 集合, List 集合中本应该只存储 B 类对象的, 现在却不可预料地放入了 C 对象, 这显然会引起麻烦, 所以这种情况是不允许的. 第二种情况, 让 List 引用指向 List 对象, 这样肯定也是不可以的, 因为已存在的 List 集合中, 可能已经存在了 A 类的非 B 类子类对象, 如 C 类对象, 这样也不能保证从 List 集合中取出的元素一定就是 B 对象. 但是, 确实存在需要使用泛型继承关系的情况, 看如下代码: public void processElements(List<A> elements){
- for(A o : elements){
- System.out.println(o.getValue()); // 这里假设的 A 类存在 getValue() 方法
- }
}我们希望定义一个类, 让其能处理所有指定类型元素的 List , 如 processElements(List elements) 方法, 既可以处理 List, 也可以处理 List, 但是如上所述, List 和 List 根本就不存在任何继承关系.
这时就可以使用类型通配符如 <?> 来完成此需求. 类型通配符 有 3 中类型通配符用法, 如下代码: List<?> listUknown = new ArrayList<A>(); // 任何类型元素都可接受
- List<? extends A> listUknown = new ArrayList<A>(); // 可接受 A 子类类型元素
- List<? super A> listUknown = new ArrayList<A>(); // 可接受 A 父类类型元素 还可以有多个限定, 比如限定要同时可比较和可序列化, 就可以使用 <T extends Comparable & Serializable> 无边界类型通配符<?> List<?> 表示未知类型的 List . 这可以是 List,List 或 List 等. 由于不知道 List 的类型, 所以只能从集合中读取, 不能放入新元素, 并且只能将读取的元素视为 Object 类型, 如下例: public void processElements(List<?> elements){
- for(Object o : elements){
- System.out.println(o);
- }
} 现在 processElements() 方法可以使用任何泛型参数的 List 集合作为参数, 如下: List<A> listA = new ArrayList<A>();
processElements(listA); 上界类型通配符<? extends A> 这样将表示可接受的泛型参数, 必须是 A 类或者 A 类的子类, A 类就是最大的范围, 所以叫做上界.
定义如下方法: public void processElements(List<? extends A> elements){
- for(A a : elements){
- System.out.println(a.getValue());
- }
}这样, 它可以接收 List, List 或 List 集合作为参数, 如下: List<A> listA = new ArrayList<A>();
- processElements(listA);
- List<B> listB = new ArrayList<B>();
- processElements(listB);
- List<C> listC = new ArrayList<C>();
processElements(listC); processElements(List<? extends A> elements) 方法仍然无法插入元素到列表中, 因为不知道传入的 List 中的元素的具体类型, 它可能是 A, B 或 C 类 , 如果可以插入, 那么取出来的时候就无法确认, 强制转换就会失败. 但是可以读取元素, 因为 List 里面保存的元素一定是 A 类或其子类对象, 转换成 A 类不会报错. 下界类型通配符 <? super A> 这样将表示可接受的泛型参数, 必须是 A 类 (A 类的子类也属于 A 类) 或者 A 类的父类, A 类就是最小的范围, 所以叫做下界.
当知道 List 中的元素肯定是 A 类或 A 的父类时, 可以安全地将 A 类的对象或 A 的子类对象 (例如 B 或 C) 插入 List 中. 下面是一个例子: public static void insertElements(List<? super A> list){
- list.add(new A());
- list.add(new B());
- list.add(new C());
} 此处插入的所有元素都是 A 类对象或 A 类的父类的对象. 由于 B 和 C 都继承了 A , 如果 A 有一个父类, B 和 C 也将是该父类的对象. 现在可以使用 List 或类型为 A 的父类调用 insertElements() , 如下类: List<A> listA = new ArrayList<A>();
- insertElements(listA);
- List<Object> listObject = new ArrayList<Object>();
insertElements(listObject); 但是, insertElements() 方法无法从 List 中读取元素, 除非它将读取到的对象强制转换为 Object . 调用 insertElements() 时, List 中已经存在的元素可以是 A 类或其父类的任何类型, 但不可能确切地知道它是哪个类. 由于 Java 所有的类都是 Object 的子类, 因此如果将它们转换为 Object , 则可以从列表中读取对象. 如下代码是正确的: Object object = list.get(0); 但是如下代码是错误的, 因为父类对象不能转换为子类对象: A object = list.get(0); 泛型擦除 Java 的泛型是伪泛型, 几乎仅仅提供编译时检查, 泛型确保了只要在编译时不出现错误, 运行时就不出现强制转换异常. 在编译完成后, 所有的泛型信息都会被擦除掉. List 和 List 等类型在编译后都会变成 List , 泛型附带的类型信息对 JVM 是不可见的. 定义 2 个分别处理 List 和 List 元素的 processElements() 函数, 代码如下: public void processElements(List<String> elements) {
- }
- public void processElements(List<Integer> elements) {
} 编译器会报如下错误:
Error:(12, 17) java: 名称冲突: processElements(java.util.List<java.lang.Integer>)和 processElements(java.util.List<java.lang.String>)具有相同疑符 意思就是我们定义了 2 个重复的方法, 即两个方法的参数是一样的, 说明虽然泛型参数不一样, 但到底 List 和 List 是一个东西, 它们的原始类型是一样的, 都是同一个 List 类对象, 可以使用如下代码验证: Class strListClass = new ArrayList<String>().getClass();
- Class intListClass = new ArrayList<Integer>().getClass();
- System.out.println(strListClass); // class java.util.ArrayList
- System.out.println(intListClass); // class java.util.ArrayList
- System.out.println(strListClass == intListClass); // ture 原始类型 (raw type) 就是擦除 (crased) 掉泛型信息后的真正类型, 泛型参数信息会被擦除, 运行时最终使用 Object 或具体的限定类. 如 Class strListClass = new ArrayList<String>().getClass(); 编译后会变成 Class strListClass = new ArrayList().getClass(); Java 为什么只把泛型信息留在编译阶段?
Java 不能实现真正泛型的原因? - RednaxelaFX 的回答 - 知乎 其他 JDK 文档中经常能看到 T,K,V,E,N 等类型参数, 实际上这些符号随意选择使用也是没问题的, 甚至可以使用如 A,B 等, 但建议和 JDK 文档风格保持一致, 至少得让人容易理解.
常见各符号的含义: T:type
- K:key
- V:value
- E:element
N:Number 泛型数组 在 Java 中不能创建一个确切泛型类型的数组, 如下代码是不允许的: List<String>[] strLists = new ArrayList<String>[8]; 将会报错: Error:(11, 27) java: 创建泛型数组. 但是使用通配符创建泛型数组是可以的, 比如下面代码是被允许的: List<?>[] strLists = new ArrayList<?>[8]; 为什么?
Java 为什么不支持泛型数组? - 蜗牛学院的回答 - 知乎 本文参考:
菜鸟教程
- googletag.cmd = googletag.cmd || []; googletag.cmd.push(function() {
- googletag.defineSlot('/1090369/C2', [468, 60], 'div-gpt-ad-1539008685004-0').addService(googletag.pubads());
- googletag.pubads().enableSingleRequest();
- googletag.enableServices();
- }); if (new Date()>= new Date(2018, 9, 13)) {
- googletag.cmd.push(function() {
- googletag.display('div-gpt-ad-1539008685004-0');
- });
- } fixPostBody();
- setTimeout(function () {
- incrementViewCount(cb_entryId);
- }, 50);
- deliverAdT2();
- deliverAdC1();
- deliverAdC2();
- loadNewsAndKb();
- loadBlogSignature();
- LoadPostInfoBlock(cb_blogId, cb_entryId, cb_blogApp, cb_blogUserGuid);
- GetPrevNextPost(cb_entryId, cb_blogId, cb_entryCreatedDate, cb_postType);
- loadOptUnderPost();
- loadBlogNews();
- loadBlogDefaultCalendar();
来源: https://www.cnblogs.com/czwbig/p/10096519.html