Java学习整理之Java泛型
Posted Sup_Heaven
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习整理之Java泛型相关的知识,希望对你有一定的参考价值。
一、一个困惑
泛型我们经常会看到或者使用,这里首先记录一个自己曾经的困惑,下面的两个泛型方法有什么区别?
public void dosomething(T t)
public <T> void dosomething(T t)
看了下面的例子就懂了。
class Fruit
public String toString()
return "Fruit";
class Apple extends Fruit
public String toString()
return "Apple";
class Dog
public String toString()
return "Dog";
class Show<T>
void show_1(T t)
System.out.println("show_1 " + t.toString());
<E> void show_2(E e)
System.out.println("show_2 " + e.toString());
<T> void show_3(T t)
System.out.println("show_3 " + t.toString());
public static void main(String[] args)
Show<Fruit> o = new Show<Fruit>();
Fruit f = new Fruit();
Apple a = new Apple();
Dog d = new Dog();
System.out.println("show_1 演示________________________");
o.show_1(f);
o.show_1(a);
/*
* o.show_1(d); 把这行代码去掉注释,是不能编译通过的。因为在Show<T>中已经限定了全局的T为Fruit,
* 所以此时的T只能是Fruit或者其子类,所以不能再加入Person;
*/
System.out.println("show_2 演示________________________");
o.show_2(f);
o.show_2(a);
/*
* o.show_2(d); 正确。因为返回值void前面加了<E>,说明它与类中的<T>是可以不一样的,并不要求是Fruit或者其子类,可以是任何类型。
*/
o.show_2(d);
System.out.println("show_3 演示________________________");
o.show_3(f);
o.show_3(a);
/*
* o.show_3(d); 正确。因为返回值void前面加了<T>,说明它与类中的<T>是可以不一样的,并不要求是Fruit或者其子类,可以是任何类型。
*/
o.show_3(d);
/*总结:只要类中限定了<T>(<E>,<R>等名字随便取,只要整个类中的一致),那么当初始化话该类的时候绑定的是什么,后面就各个方法中就只能传入相应的类型了,
除非在该方法前在写一个<T>(<E>,<R>等名字随便取,只要整个方法中的一致)表示该方法中可以传入的类型是随便的。*/
二、一些概念
<? extends T>和<? super T>
JAVA5.0的新的概念。由于它们的外表导致了很多人误解了它们的用途:
1.<? extends T>首先你很容易误解它为继承于T的所有类的集合,这是大错特错的,相信能看下去你一定见过或用过List<? extends T>吧?为什么我说理解成一个集合是错呢?如果理解成一个集合那为什么不用List<T>来表示?
所以<? extends T>不是一个集合,而是T的某一种子类的意思,记住是一种,单一的一种,问题来了,由于连哪一种都不确定,带来了不确定性,所以是不可能通过 add()来加入元素。你或许还觉得为什么add(T)不行?因为<? extends T>是T的某种子类,能放入子类的容器不一定放入超类,也就是没可能放入T。自己的理解是:由于传入的可以T类型的子类,但编译时编译器不知道运行时将会传进来的是什么,同时Java中不允许父类直接复制给子类的引用,就算强制类型转换了也不行(当然,如果A是B的父类,A a=new B(),此时是可以B B=(B)a是可以的),所以这里add什么都不能加了,除了可以代表一切类型的null。下面的代码说明了这一点。
package com.lxq.generics;
import java.util.ArrayList;
import java.util.List;
class Fruit
class Apple extends Fruit
class RedApple extends Apple
public class TestGenerics
public static void main(String[] args)
TestGenerics tg=new TestGenerics();
tg.test1(new ArrayList<Fruit>());
tg.test2(new ArrayList<RedApple>());
public void test1(List<? super Apple> apples)
/*既然传进来时apples是new ArrayList<Fruit>(),为什么不能添加
apples.add(new Fruit());
apples.add((Apple) new Fruit());*/
apples.add(new Apple());
apples.add(new RedApple());
public void test2(List<? extends Apple> apples)
/*既然传进来时apples是new ArrayList<RedApple>(),为什么不能添加
apples.add(new Apple());
apples.add(new RedApple());
apples.add(new Object());*/
apples.add(null);
2.<? super T>这里比较容易使用,没<? extends T>这么多限制,这里的意思是,以T类为下限的某种类,简单地说就是T类的超类。但为什么add(T)可以呢?因为能放入某一类的容器一定可以放入其子类,多态的概念。 自己的理解:由于<? super T>,那么最后传入的是T的父类或T,本例中就是Apple类或者Apple的父类,所以只能添加Apple及其子类,比如添加Fruit类时又会出现将Fruit类赋值给Apple类型的引用(传进来的可能是Apple类型的哦)。
擦除
也许泛型最具挑战性的方面是擦除(erasure),这是 Java 语言中泛型实现的底层技术。擦除意味着编译器在生成类文件时基本上会抛开参数化类的大量类型信息。编译器用它的强制类型转换生成代码,就像程序员在泛型出现之前手工所做的一样。 区别在于,编译器开始已经验证了大量如果没有泛型就不会验证的类型安全约束。通过擦除实现泛型的含意是很重要的,并且初看也是混乱的。尽管不能将List<Integer> 赋给List<Number>,因为它们是不同的类型, 但是 List<Integer> 和 List<Number> 类型的变量是相同的类!要明白这一点,请评价下面的代码:
List<Number> l1=new ArrayList<Number>();
List<Integer> l2=new ArrayList<Integer>();
System.out.println(l1.getClass());
System.out.println(l2.getClass());
System.out.println(l1.getClass()==l2.getClass());
编译器只为 ArrayList生成一个类。当生成了 ArrayList的字节码时,将很少剩下其类型参数的的跟踪。
当生成泛型类的字节码时,编译器用类型参数的擦除替换类型参数。对于无限制类型参数(<V>),它的擦除是 Object。对于上限类型参数(<K extends Comparable<K>>),它的擦除是其上限(在本例中是 Comparable)的擦除。 对于具有多个限制的类型参数,使用其最左限制的擦除。如果检查生成的字节码,您无法说出 List<Integer> 和 List<String> 的代码之间的区别。类型限制 T 在字节码中被 T 的上限所取代,该上限一般是 Object。
多重限制
一个类型参数可以具有多个限制。当您想要约束一个类型参数比如说同时为 Comparable 和 Serializable 时,这将很有用。多重限制的语法是用“与”符号分隔限制: class C<T extends Comparable<? super T> & Serializable>
通配符类型可以具有单个限制 —— 上限或者下限。一个指定的类型参数可以具有一个或多个上限。具有多重限制的类型参数可以用于访问它的每个限制的方法和域。
类型形参和类型实参
在参数化类的定义中,占位符名称(比如 Collection<V> 中的 V)叫做类型形参(type parameter),它们类似于方法定义中的形式参数。在参数化类的变量的声明中,声明中指定的类型值叫做类型实参(type argument),它们类似于方法调用中的实际参数。但是实际中二者一般都通称为“类型参数”。所以给出定义: interface Collection<V> ... 和声明:Collection<String> cs = new HashSet<String>();
那么,名称 V(它可用于整个 Collection 接口体内)叫做一个类型形参。在 cs 的声明中,String 的两次使用都是类型实参(一次用于 Collection<V>,另一次用于 HashSet<V>)。 关于何时可以使用类型形参,存在一些限制。大多数时候,可以在能够使用实际类型定义的任何地方使用类型形参。但是有例外情况。不能使用它们创建对象或数组,并且不能将它们用于静态上下文中或者处理异常的上下文中。还不能将它们用作父类型(class Foo<T> extends T),不能用于 instanceof 表达式中,不能用作类常量。 类似地,关于可以使用哪些类型作为类型实参,也存在一些限制。类型实参必须是引用类型(不是基本类型)、通配符、类型参数,或者其他参数化类型的实例化。所以您可以定义 List<String>(引用类型)、List<?>(通配符)或者 List<List<?>>(其他参数化类型的实例化)。在带有类型形参 T 的参数化类型的定义中,您也可以声明 List<T>(类型形参)。
三、一些示例
泛型上边界
flist1编译报错,不兼容的参数类型,集合认为虽然Apple继承自Fruit,因为泛型参数在声明时给定之后就被限制了,无法随着具体的初始化实例而动态改变,为解决这个问题,泛型引入了通配符”?”。声明List<? extends Fruit>,那么初始化的时候只要是Fruit及其子类即可,所以flist3和flist4就是正确的。
package com.lxq.generics;
import java.util.ArrayList;
import java.util.List;
class Fruit
class Apple extends Fruit
class RedApple extends Apple
class Orange extends Fruit
public class TestGenerics
//Type mismatch: cannot convert from ArrayList<Apple> to List<Fruit>
//List<Fruit> flist1 = new ArrayList<Apple>();
List<Fruit> flist2 = new ArrayList<Fruit>();
List<? extends Fruit> flist3 = new ArrayList<Apple>();
List<? extends Fruit> flist4 = new ArrayList<Fruit>();
泛型下边界
通过<? super >限制了传入的List元素只能是Apple的父类,但是形参的类型实际上是List<Apple>的,所以apples.add((Apple) new Fruit());才一定要类型转换。如果一个类是泛型类也就是声明时是Classname<T>,那么泛型下边界还可以使用<?super T>,但是注意不能使用<Tsuper A>,即super之前的只能是泛型通配符,
package com.lxq.generics;
import java.util.ArrayList;
import java.util.List;
class Fruit
class Apple extends Fruit
class RedApple extends Apple
class Orange extends Fruit
public class TestGenerics
public static void main(String[] args)
TestGenerics tg=new TestGenerics();
tg.test(new ArrayList<Fruit>());
public void test(List<? super Apple> apples)
//apples.add((Apple) new Fruit());运行时会报错Fruit cannot be cast to Apple
apples.add(new Apple());
apples.add(new RedApple());
无边界通配符
泛型的通配符也可以不指定边界,没有边界的通配符意思是不确定参数的类型,编译时泛型檫除类型信息,认为是Object类型。如:
package com.lxq.generics;
import java.util.ArrayList;
import java.util.List;
public class UnboundedWildcard
static List list1;
static List<?> list2;
static List<? extends Object> list3;
static void assign1(List list)
list1 = list;
list2 = list;
list3 = list; //有未检查转换警告
static void assign2(List<?> list)
list1 = list;
list2 = list;
list3 = list;
static void assign3(List<? extends Object> list)
list1 = list;
list2 = list;
list3 = list;
public static void main(String[] args)
assign1(new ArrayList());
assign2(new ArrayList());
assign3(new ArrayList()); //有未检查转换警告
assign1(new ArrayList<String>());
assign2(new ArrayList<String>());
assign3(new ArrayList<String>());
List<?> wildList = new ArrayList();
assign1(wildList);
assign2(wildList);
assign3(wildList);
List和List<?>的区别是:List是一个原始类型的List,它可以存放任何Object类型的对象,不需要编译时类型检查。List<?>等价于List<Object>,它不是一个原始类型的List,它存放一些特定类型,只是暂时还不确定是什么类型,需要编译时类型检查。因此List的效率要比List<?>高。
四、几个注意点
实现泛型接口注意事项
由于泛型在编译过程中檫除了参数类型信息,所以一个类不能实现以泛型参数区别的多个接口,类Hourly无法编译,因为由于泛型类型檫除,Payable<Employee>和Payable<Hourly>在编译时是同一个类型Payable,因此无法同时实现一个接口两次。
interface Payable<T>
class Employee implements Payable<Employee>
class Hourly extends Employee implements Payable<Hourly>
泛型方法重载注意事项
由于泛型在编译时将参数类型檫除,因此以参数类型来进行方法重载在泛型中要特别注意,无法通过编译,因为泛型檫除类型信息,上面两个方法的参数都被看作为Object类型,使用参数类型已经无法区别上面两个方法,因此无法重载。
public class GenericMethod<W,T>
<span style="white-space:pre"> </span>void f(List<T> t)
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>void f(List<W> v)
<span style="white-space:pre"> </span>
参考原文:
Java泛型-类型擦除http://blog.csdn.net/caihaijiang/article/details/6403349
Java 泛型http://www.cnblogs.com/friends-wf/p/3582841.html
简述泛型通配符<? extends T>和<? super T>http://hi.baidu.com/augustus_blog/item/d9331b3469b65a1d9dc65e69
《Java编程思想》学习笔记7——泛型编程基础http://blog.csdn.net/chjttony/article/details/6785221
《Java编程思想》学习笔记8——泛型编程高级http://blog.csdn.net/chjttony/article/details/6801406
以上是关于Java学习整理之Java泛型的主要内容,如果未能解决你的问题,请参考以下文章