Java泛型
泛型
-
定义:意为参数化类型,即传入的参数是个类型,如集合
List<String>
,就是一个带类型参数的泛型接口,泛型允许在定义类、接口、方法时使用类型作为形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。 -
语法:Java7之后,在调用对象的构造器的时候,可以省略泛型中的参数类型,而只留下菱形语法,例如
List<String> books = new ArrayList<>();
-
泛型的声明和使用:
声明:需要指出形参,如:
public class Apple<T>
使用:使用泛型接口、父类时,不能包含类型形参,需要指出具体类型,或者省略不写
省略不写:
- 调用构造器时的菱形语法
- 从泛型派生子类时,如果省略菱形:此时T会被当做
Object
处理
-
运行类型:
不论传入何种类型形参,同一容器的泛型类型实例类型始终相同,即会被当做实例化(运行类型)的非泛型类处理。
-
编译类型:
对编译器来说,
List<Object>
和List<String>
没有继承和派生的关系,他们类型也不相等。 -
类型通配符
-
当需要向定义的方法传递泛型参数时,可以使用类型通配符
?
作为类型形参。示例
public void test(List<?> c) { for (int i = 0;i < c.size();i++) { System.out.println(c.get(i)); } }
此时,传入的类型形参会在方法体内被当作
Object
处理。 -
类型通配符的上限
如果需要限定类型的范围,可以使用被限制的类型通配符,如:
List<? extends Father>
,能够接收所有Father类的派生类和他自己,并被编译器默认当做Father类对象。特别地,向集合等容器添加元素时,必须是能让编译器知道的确切类型(确切的编译类型,使用类型通配符无法添加不确定的子类元素)才能添加。
-
类型形参的上限
在定义类、接口、方法时,也可以采用和通配符限制类似的写法如:
public class Apple<T extends Number>
,来限制类型形参只能是某一类的派生或者它本身。设置多个上限时,可以这样写:public class Apple<T extends Number & java.io.Serializable>
-
类型通配符的下限
类似通配符上限,
<? super Type>
,表示它必须是Type
或者Type
的父类。应用:
TreeSet<E>
,(TreeMap
类似)他有个构造器是TreeSet(Comparator<? super E> c)
,其中接口Comparator定义及举例如下://定义 public interface Comparator<T> { int compare(T fst, T snd); } //举例 public class TreeSetTest { public static void main(String[] args) { //以Object为形参比较 TreeSet<String> ts1 = new TreeSet<>( new Comparator<Object>() { public int compare(Object fst, Object snd) { return fst.hashCode() > snd.hashCode() ? 1 : fst.hashCode() < snd.hashCode() ? -1 : 0; } } ); //以String为形参做比较 TreeSet<String> ts2 = new TreeSet<>( new Comparator<String>() { public int compare(String first, String second) { return first.length() > second.length() ? -1 : first.length() < second.length() ? 1 : 0; } } ); } }
-
-
泛型方法
由于当函数参数带通配符时,无法向集合中添加不确定类型的元素这一缺陷,引入泛型方法。
即,在方法签名(方法名称和参数类型)中显式声明类型形参。
格式如下
public static <T, S> void from ArrayToCollection(T[] a, Collection<T> c) {//此处的S表示可以添加不止一个类型形参 for (T o : a) { c.add(o); } }
和通配符的区别:
-
如果类型之间有依赖关系(比如方法体内的其他变量类型依赖于传入的类型形参的类型),那么应该使用泛型方法;
如果仅仅只是在不同的调用点传入不同的实际类型,而类型之间没有相互依赖的关系,则应该使用通配符,但要保证目标集合的元素类型是添加的元素类型的父类。
-
类型通配符既可以在方法签名中定义形参类型,也可以用于定义变量类型,而泛型方法必须在方法签名中显式声明,才能在方法体中使用。
泛型方法重载:
如果使用了设置上下限的声明,那么这两个声明不能有交集,即使定义时不会出错,调用方法时则会由于具体类型不明,无法确定具体的方法而报错。
-
-
泛型构造器
Java7以后,可以将构造器写为泛型方法:
class Foo<E> { public <T> Foo(T t) { System.out.println(t); } } ....... public static void main(String args[]) { Foo<String> f = new <Integer> Foo<String>(5); //得到的t是Integer类型 //此处String指定的是Foo类声明中的E形参,故泛型构造器在调用的时候,其类型形参的值取决于前面的值 //T形参为Integer //此处由于显示指明了泛型构造器中生命的类型形参为Integer,故无法使用“菱形”语法 }
-
擦除与转换
擦除
如果不为泛型类指定实际的参数类型,那么该类型参数被称为
raw type
,默认为第一个上限类型(没指定就是Object
),比如List<String>
类型转换为List
类型,丢失类型参数信息,那么上限将变为Object
。 -
泛型数组
泛型设计原则——若编译未提出“[unchecked]未经检查的转换警告”,那么运行时就不应该引发
ClassCastException
警告,所以数组元素类型不能包含类型变量或类型形参,应该使用容器嵌套或者自定义一个新的类来实现相应的功能。如果允许这么做,下面的代码就会出问题
List<String>[] lsa = new List<String>[10]; // illegal Object[] oa = lsa; // OK because List<String> is a subtype of Object List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); oa[0] = li; String s = lsa[0].get(0); //error! ClassCastException
但java允许定义无上限的通配符泛型数组,即如下的代码编译可以通过,但是运行时会抛出异常
List<?>[] lsa = new ArrayList<?>[10]; Object[] oa = lsa; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); oa[0] = li; String s = (String)lsa[0].get(0); //error! ClassCastException
此外,创建元素类型是类型变量的数组对象也无法编译:
<T> T[] makeArray(Collection<T> coll) { return new T[coll.size()];//此处会编译错误 }
转载:https://blog.csdn.net/deltapluskai/article/details/102489640