泛型
泛型:对后续所有操作的类型做约束,对后续操作起作用,对之前的不起作用; 对类型进行约束;
父 ----> 子,从范围上,父范围小,子范围大;把范围小的给范围大的,
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了“类型形参”,这个类型形参将在声明变量、创建对象时确定,即传入实际的类型,我么称为“类型实参”。我们把这个“参数化的类型”称为泛型(Generic)。
我们可以为任何类和接口增加泛型声明,并不是只有集合类才可以使用泛型声明。
ArrayList<E>的<E>就是“类型形参”,ArrayList<String>的<String>就是“类型实参”,String类型就是用来确定E的类型用的。
为了区别,我们可以将int max(int a, int b)中a,b称为数据形参,将 int max = max(3,6);中3,6称为数据实参。
泛型形参的命名一般使用单个的大写字母,如果有多个类型形参,那么使用逗号分隔,例如:Map<K,V>。
常见字母(见名知意):
例如定义学生类的成绩score不止一个类型:
... public static void main(String[] args) { Student<Integer> stu = new Student(1, "kris", 99); Student<String> stu1 = new Student(2, "smile", "优秀"); Student<Double> stu2 = new Student(5, "aa", 89.9); } } class Student<T>{ private int id; private String name; private T score; ..... }
可以在定义接口、类时指定类型形参,类型形参在整个接口或类体中可以当成类型使用,几乎所有可使用其他普通类型的地方都可以使用这种类型形参,例如:属性类型、方法的形参类型、方法返回值类型等。
但是泛型类或泛型接口上的泛型形参,不能用于声明静态变量,也不能用在静态方法中,那是因为静态成员的初始化是随着类的初始化而初始化的,此时泛型实参无法指定,那么泛型形参的类型就不确定。
当创建带泛型声明的类时,为该类定义构造器时,构造器名还是原来的类名,不需要增加泛型声明。例如:Student<T>类定义的构造器依然是Student(),而不是Student<T>,但调用构造器时却可以使用Student<T>的形式,而且应该为T类型形参传入实际的类型实参。
泛型实参的要求
首先泛型实参必须是引用数据类型,不能是基本数据类型,可以指定为包装类型,因为集合中只能存储对象。
什么时候指定泛型实参?
(1)在用泛型类、接口声明变量时
(2)在继承泛型类或实现泛型接口时,如果子类不延续使用该泛型,那么必须明确指定实际类型。此时子类不再是泛型类了
(3)在创建泛型类对象时
public class EmployeeManager { private ArrayList<Employee> lis; public static void test(ArrayList<String> lis){ ArrayList<String> list = new ArrayList<String>(); ArrayList<String> li = new ArrayList<>();//JDK1.7之后支持如下简化写法: } } //继承泛型类 class MyArrayList extends ArrayList<String>{//此处ArrayList<String>就不能写成ArrayList<E> //因为MyArrayList不再是泛型类了,因此E必须给出具体的类型 } //实现泛型接口 class Employee implements Comparable<Employee>{ @Override public int compareTo(Employee o) { // TODO Auto-generated method stub return 0; } }
继承泛型类、实现泛型接口时,想要继续保留父类、父接口的泛型,必须在父类、父接口和子类、子接口中都要保留泛型。
设定泛型形参的上限
如果泛型形参没有设定上限,那么泛型实参可以是任意引用数据类型。如果泛型形参设定了上限(例如:T extends 父类上限),那么只能指定为该父类本身或其各子类类型。
在一种更极端的情况下,程序需要为形参设定多个上限(至多有一个父类上限,可以有多个接口上限)表明该类型形参必须是其父类的子类(包括是父类本身也行),并且实现多个上限接口。
与类同时继承父类、实现接口类似的是:为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。
class Student<T extends Number & java.io.Serializable> { //...省略其他代码 }
.... Student<Integer> stu = new Student(1, "kris", 99); //Student<String> stu1 = new Student(2, "smile", "优秀");//以下代码编译报错,因为String不是Number的子类 Student<Double> stu2 = new Student(5, "aa", 89.9); ArrayList<Integer> list = new ArrayList<>(); class Student<T extends Number>{ private int id; private String name; private T score; public Student(int id, String name, T score) { super(); this.id = id; this.name = name; this.score = score; .... }
定义泛型方法
在定义类、接口时可以使用类型形参,在该类的方法和属性定义、接口的方法定义中,这些类型形参可被当成普通类型来用。但是,在另外一些情况下,(1)如果我们定义类、接口时没有使用类型形参,但是某个方法定义时,想要自己定义类型形参;(2)另外我们之前说类和接口上的类型形参是不能用于静态方法中,那么当某个静态方法想要定义类型形参。那么,JDK1.5之后,还提供了泛型方法的支持。
* 二、泛型方法 * 1、什么时候使用泛型方法 * (1)因为刚才说,泛型类或泛型接口上的泛型形参是不能用于静态成员的, * 那么当静态方法需要用到泛型时,只能用泛型方法。 * * (2)如果泛型类或泛型接口上的泛型形参,但是对于某个方法来说,不适用,或这个类或接口本身不是泛型类和泛型接口, * 这个方法想要单独声明泛型,那么也得用方法方法,这个方法可以是非静态的 * * 2、如何声明 * 【修饰符】 <泛型形参列表> 返回值类型 方法名(方法的形参列表) { * } * 泛型方法中声明的泛型形参,只能用在当前方法中。 * * 需要实现这样的一个方法,该方法负责将一个数组的所有元素添加到一个Collection集合中 * * 3、泛型方法的泛型形参是什么时候指定的 * 调用时 * * 4、可以给泛型方法的泛型形参指定上限 * 【修饰符】 <泛型形参 extends 父类上限> 返回值类型 方法名(方法的形参列表) { * } * * 需求:接收一个集合(里面都是图形),打印所有图形的面积
ArrayList<String> list = new ArrayList<String>(); .. public <T extends Graphic> void print(ArrayList<T> list){ for (T t : graphics) { System.out.println(t.getArea()); } }
String[] array = {"Hello", "java"}; Collection<String> c = new ArrayList<String>(); //Object[] arr = array;//这样子是多态引用是可以的 //Collection<Object> coll = c;//但右边ArrayList<String>,左边是不接收的;报错 fromArrayToCollection(array, c); //编译报错 public static void fromArrayToCollection(Object[]a, Collection<Object> c){ for(Object object : a){ c.add(object); } } //可以这样子写;//该方法负责将一个数组的所有元素添加到一个Collection集合中 public static <T> void copy(T[] arr, Collection<T> coll){ for (int i = 0; i < arr.length; i++) { coll.add(arr[i]); } }
泛型擦除
* 1、泛型的擦除: * 当我们使用泛型类或泛型接口时,如果没有指定泛型的实参,那么它会出现泛型擦除的现象, * 如果泛型形参有上限,就按照第一个上限处理,如果没有上限,就按照Object处理。 * * 2、泛型类指定为不同泛型实参时,运行时是同一种类型 * * 3、instanceof后面不支持泛型类;// 由于系统中并不会真正生成泛型类 * * 4、泛型类不能创建数组; ArrayList<String>[] array = new ArrayList<String>[5]; //编译错误; this.arr = new T[length]; * * 5、try..catch的catch中不能使用泛型
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public class TestErase { public static void main(String[] args) { ArrayList list = new ArrayList<String>();//ArrayList<String>被转换为了ArrayList list.add("kris"); list.add("smile"); for (Object object : list) { System.out.println(object); } Object object = list.get(1); //泛型被擦除,按照默认上限Object处理 Student s = new Student("kk", 99); //Student<Number> Number score = s.getScore();//泛型被擦除,按照第一个上限Number处理 } } class Student<T extends Number>{ private String name; private T score; public Student(String name, T score) { super(); this.name = name; this.score = score; } public T getScore() { return score; } }
public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass());//class java.util.ArrayList System.out.println(list2.getClass());//class java.util.ArrayList System.out.println(list1.getClass() == list2.getClass());//true ArrayList<Object> list = new ArrayList<String>(); //错误的 /*ArrayList<String>不是ArrayList<Object>的子类, 因为他们的运行时类型都是ArrayList,因此不允许如下赋值操作。 这点和数组不同,因为数组是要生成新的Class对象的,String[]仍然是Object[]的子类,因此允许如下赋值操作。 Object[] arr = new String[5];*/ }
通配符
* 通配符:? * 1、类型通配符只能用于方法中,不能用在泛型类或泛型接口的声明上。 * 2、形式: * (1) <?> 代表的是任意类型 * (2)<? extends 上限Upper> 代表的是Upper的类型或它的子类 * (3)<? super 下限Lower> 代表的是Lower的类或它的父类
当我们声明一个方法时,某个形参的类型是一个泛型类或泛型接口类型,但是在声明方法时,又不确定该泛型实际类型,我们可以考虑使用类型通配符。
//使用类型通配符 public static void test(List<?> c){ //List c这种形式使用List接口时没有传入实际类型参数,这将引起泛型警告。 for (int i = 0; i < c.size(); i++) {//List<Object> c这种太局限了,调用时,只能传List<Object>,List<Object>不能接收List<String>等其他集合 System.out.println(c.get(i)); } } //声明一个泛型方法,需要声明泛型形参T。 public static <T> void test2(List<T> c){ for (int i = 0; i < c.size(); i++) { System.out.println(c.get(i)); } }
通配符的List仅表示它可以接受指定了任意泛型实参的List,并不能把元素加入其中,例如如下代码将会引起编译错误:
public static void test(List<?> c, String str){ c.add(str); }
不知道上面程序中c集合里元素的类型,所以不能向其中添加对象,除了null对象,因为它是所有引用数据类型的实例。
test2方法带泛型的List,表示该集合的元素类型是T,因此允许T系列的对象加入其中,例如如下代码是可行的:
public static <T> void test(List<T> c, T t){ c.add(t); }
如果不涉及添加元素到带泛型的集合中,那么两种方式都可以,如果涉及到添加元素到带泛型的集合中,使用类型通配符<?>的不支持。
当直接使用List<?>这种形式时,即表明这个List集合接收泛型实参指定为任意类型的List。
有时候我们只希望接收某些类型的List。
例如:一个图形的抽象父类Graphic,两个子类Circle和Rectangle。接下来我们想定义一个方法,可以打印不同图形的面积。
但是,List<Graphic>的形参只能接收List<Graphic>的实参,如果想要接收List<Circle>,List<Rectangle>的集合,可以使用List<?>。
但是这样有两个问题,一个是List<?>可以接收任意类型,不仅仅图形,第二个是需要强制类型转换。为了解决这个问题,Java允许设定通配符的上限:<? extends Type>,这个通配符表示它必须是Type本身,或是Type的子类。
public static void printArea(List<? extends Graphic> graphic){ for (Graphic g : graphic) { System.out.println(g.getArea()); } } /*与前面的完全相同,因为我们不知道这个受限制的通配符的具体类型,所以我们不能把Graphic对象或其子类对象加入这个泛型集合中。 如果要需要将Graphic对象或其子类对象加入这个泛型集合,那么就只能用泛型方法了,*/ public static void printArea(List<? extends Graphic> graphics){ graphics.add(new Circle());//编译错误,因为不知道?的具体类型,也可能是Rectangle }
返回的T是Object,也就是说,程序在复制集合元素的过程中,丢失了src集合元素的类型String。
对于上面的copy方法,可以这样理解两个集合参数之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素类型与src的元素类相同或是它的父类即可。为了表示这种约束关系,Java允许设定通配符的下限:<? super Type>,这个通配符表示它必须是Type本身或是Type的父类。
public static void main(String[] args) { ArrayList<String> src = new ArrayList<String>(); src.add("kris"); //src -- >dest ArrayList<Object> dest = new ArrayList<Object>(); String last = copy(dest, src); System.out.println(last); } /* 实现将src集合里元素复制到dest集合中的功能,因为dest集合需要接受src的所有元素, 所以dest集合元素的类型应该是src集合元素的父类。 为了表示两个参数之间的类型依赖,考虑同时使用通配符、泛型形参类实现该方法。*/ /* public static <T> void copy (Collection<T> dest, Collection <? extends T> src){ for (T t : src) { dest.add(t); } }*/ public static <T> T copy (Collection<? super T> dest, Collection <? extends T> src){ T last = null; for (T t : src) { dest.add(t); } return last; }