java高级之泛型
初识泛型
走进泛型
方法形参实参类比
-
从一个方法的实参和形参说起:
//现在定义了一个add方法,两个参数,参数类型为Integer,i、j是形参。 private Integer add(Integer i, Integer j) { return i + j; } @Test public void testAdd() { int a = 3; int b = 7; //调用该方法 传入实参a、b,也就是当调用该方法时传入实参时,才知道i、j具体的数值 int c = add(a, b); System.out.println("a与b的和为:" + c); } //方法的结构 访问修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2) { 方法体 }
-
如上,方法
add()
的两个形参i、j的参数类型为Integer
,那能不能将这个参数类型用一种标识表示,直到这个方法被使用时,传入了相应的类型标识后才确定具体的参数类型;可以类比形参与实参。
从方法到集合
-
从一个方法过渡到集合:
- 泛型出现前,我们使用集合时是这样的:
ArrayList list = new ArrayList(); //存放int类型的数据 list.add(11); //存放double类型数据 list.add(1.23); //存放String类型的数据 list.add("AAA");
- 众所周知,这种使用方式在类型转换的时候,极易出现类型转换
ClassCastException
异常;有时,我们希望集合容器中只存在一种类型,比如:String
、Interger
类型等,这样我们在取用时,可以直接使用该类型去接收而不必担心出现类型转换异常。这时jdk1.5
之后提供了泛型来为我们解决这样的需求:
//我们希望只能存放String类型的数据 ArrayList<>():<>中不用写类型,1.7后的类型推断 ArrayList<String> list = new ArrayList<>(); //存放String类型的数据 list.add("AAA"); //存放String类型的数据 list.add("BBB"); //存放int类型的数据 编译不通过,编译器保证了只能存放String类型的数据 //list.add(11);
-
List集合的源码:可以看到
List<类型>
便是集合中的泛型,它满足我们希望只存在一种类型的需求。public interface List<E> extends Collection<E> { ... Iterator<E> iterator(); ... //将list转化为你所需要类型的数组 <T> T[] toArray(T[] a); ... boolean add(E e); ... }
-
泛型,即“参数化类型(Parameterized type)”。允许在定义类、接口时通过一个标识表示类中某个属性的类型、某个方法的返回值及参数类型。这个类型参数在使用时才能确定具体类型。(例:继承泛型类、实现泛型接口等时传入实际的类型参数,也可以理解为
类型实参
)。//泛型类ArrayList class ArrayList<E> //定义只接收String类型的参数的集合list1 ArrayList<String> list1 = new ArrayList<>(); //定义只接收Integer类型的参数的集合list2 ArrayList<Integer> list2 = new ArrayList<>(); //集合list中的add方法,看参数E, boolean add(E e) //这个参数类型E,就是在使用时,才知道具体的参数类型。 //list1对应的E是String,list2对应的E是Integer。 list1.add("AA"); list2.add(123);
自定义泛型结构
自定义泛型类
-
在类中使用泛型类型,该类称为泛型类。泛型类的基本写法如下:
class 类名称<泛型参数>{ private 泛型标识 属性名; ... }
-
看一个简单例子:
public class Dome<T> { //无参的构造方法 public Dome() { } //构造方法 注意:构造方法中没有<> public Dome(T value) { this.value = value; } //该属性类型为String private String name; //该属性类型为T private T value; /** * 返回值类型为T,声明泛型类时,声明了泛型T * 该方法用到了泛型T,但它不是泛型方法,是一个普通方法 */ public T getValue() { return value; } //该方法入参为T,由外部指定 public void setValue(T value) { this.value = value; } } //调用泛型类 @Test public void test9(){ //情况1:实例化对象时不传入泛型类型实参,不传入类型实参时,按照Object处理,但不等价于Object。 Dome dome = new Dome(); dome.setValue("AAA"); dome.setValue(123); //情况2:实例化对象时传入泛型类型实参,此时泛型起到相应的限制作用 Dome<String> domeString = new Dome<>(); domeString.setValue("BBB"); //编译不通过 //domeString.setValue(123); //情况3:泛型不同的引用不能相互赋值 Dome<Integer> domeInteger = new Dome<>(); //编译不通过 //domeInteger = domeString; //情况4:Dome<Object> 和 Dome<String> 不具有子父类关系 Dome<Object> domeObject = new Dome<>(); //编译不通过 //假设 domeObject = domeString; 能通过的话,domeObject.add(123),会混入非String类型的数据,出错。 //domeObject = domeString; }
自定义泛型接口
-
泛型接口和泛型类基本类似
public interface DemoInterface<T> { public Boolean add(T t); } /** * 情况1 实现该接口,如果不传泛型实参, * 声明DemoImpl时也要将泛型的声明加上 即DemoImpl<T> * @param <T> */ public class DemoImpl<T> implements DemoInterface<T> { @Override public Boolean add(T t) { return null; } } /** * 情况2:如果传入了泛型实参,该类中所有使用到该泛型的地方都替换为泛型实参 * 例如:add(T t) --> add(String s) */ public class DemoImpl1 implements DemoInterface<String> { @Override public Boolean add(String s) { return null; } }
自定义泛型方法
-
注意区分泛型类中的成员方法使用到了泛型和泛型方法的区别
-
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。例如:在调用
getBean()
方法时,传入了相关的类时才确定该泛型的具体类型。public static <T> T getBean(Class<T> clazz) { return beanFactory == null ? applicationContext.getBean(clazz) : beanFactory.getBean(clazz); }
-
类比着
getBean()
方法,我们可以得知泛型方法的格式:[访问权限] (static) <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
-
泛型方法举例:
/** * 1、普通类中可以有泛型方法 * public <T> :共同声明此方法为泛型方法 有<T>才说明是泛型方法,普通方法中用到了泛型不算是泛型方法 */ public <T> T demoMethod(Dome<T> dome){ return dome.getValue(); } //2、泛型类中的泛型方法 public class Dome<T> { ...... /** * 返回值类型为T,声明泛型类时,声明了泛型T * 该方法用到了泛型T,但它不是泛型方法,是一个普通方法 */ public T getValue() { return value; } ...... /** * 2:泛型类中声明泛型方法 * 2.1:可以看到使用了泛型E,泛型类中声明的泛型虽然是T,但泛型方法在调用的时候会声明泛型E,因此编译通过 */ public <E> E demoMethod2(Dome<E> dome){ return dome.getValue(); } /** * 2.2:使用了泛型T,但这个泛型T与泛型类中的T不是同一个。 所以为了区分开泛型,建议使用其他的字母 */ public <T> T demoMethod1(Dome<T> dome){ return dome.getValue(); } /** * 3、对静态方法使用<T>: 编译不通过,因为静态方法无法访问类上定义的泛型 * 如果要使用泛型,需要将静态方法定义为泛型方法 */ /*public static T temp(T value) { return value; }*/ public static <T> T temp(T value) { return value; } }
泛型特性
-
泛型只在编译阶段有效,编译过程中类型检查通过后,会将泛型相关信息擦除(类型擦除),不会进入到运行阶段。也就是说java的泛型是伪泛型,为什么不用真泛型的原因是:泛型是在
jdk1.5
之后才出现的新特性,为了兼容jdk1.5
之前的代码,故采用伪泛型。 -
使代码更加简洁,通过编译器对类型进行检查,因而不用在强制转换。
-
简单来说,就是将类型当作是参数一样传递,该类型只能是类类型,不能是基本数据类型(类型擦除),可以使用其包装类。它将类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。
-
泛型的设计原则是:只要在编译时期没有出现警告,那么运行时期就不会出现
ClassCastException
异常。 -
泛型类可能有多个参数,将多个参数一起放在尖括号内。比如集合
HashMap<K,V>
。 -
泛型类的构造器不加尖括号<>。
-
实例化后,如果指定了泛型类型,则操作原来泛型位置的结构必须与指定的泛型类型一致。不一致的话编译不通过。
-
泛型不同的引用不能相互赋值,(类型擦除),可以使用反证法证明。
-
不能对确切的泛型类型使用
instanceof
操作,(类型擦除)。 -
异常类不能是泛型的,泛型类扩展Throwable都不合法,(类型擦除)。异常在运行时被捕获或者抛出,而编译完成后,由于伪泛型问题,所有的泛型信息将会被擦除。
-
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
-
泛型结构如果是一个接口或抽象类,不能创建泛型类的对象。
-
jdk1.7
之后,泛型的类型推断。