1 为什么要有泛型?
- 集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存在的是什么类型的对象,所以在JDK1.5之前只能将元素类型设计为Object类型。
- JDK1.5之后使用泛型来解决这个问题。因为这个时候除了元素的类型不确定外,其他的部分都是确定的。例如关于这个元素是如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型叫做泛型。
- Collection<E>、List<E>等的这个<E>就是类型参数,即泛型。
2 泛型的概念
- 所谓泛型,就是允许在定义类、接口是通过一个标识标识类中的某个属性的类型或者某个方法的返回值以及参数类型。这个类型参数将在使用的时候(如继承或实现这个接口,用这个类型声明变量、创建对象)确定(即传入实际的类型参数,也称为类型实参)。
- 从JDK1.5以后,Java引入了“参数化类型(parameterized type)”的概念,允许我们在创建集合的时候再指定集合元素的类型,正如List<String>,这表明该List只能保存字符串类型的对象。
- JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
3 为什么要使用泛型,直接Object不是也可以存储数据?
- ①解决元素存储的安全性问题,比如商品、药品标签,不会弄错。
- ②解决获取数据的时候,需要强制类型转换的问题。比如不用每次拿商品和药品的时候都需要辨别。
- 在集合没有使用泛型的时候:
- 在集合中使用泛型的时候:
- Java泛型可以保证如何程序在编译的时候没有发生警告,运行的时候就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
4 在集合中使用泛型
- 示例:
package day18; import java.util.ArrayList; import java.util.List; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-23 */ public class CollectionTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张三"); list.add("李四"); list.add("王五"); list.add("赵六"); list.add("田七"); list.add("王八"); for (String s : list) { System.out.println("集合中的元素是:" + s); } } }
- 示例:
package day18; import java.util.HashMap; import java.util.Map; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-23 */ public class CollectionTest { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("张三", 21); map.put("李四", 55); map.put("王五", 12); map.put("赵六", 8); map.put("田七", 9); map.put("王八", 70); for (Map.Entry<String, Integer> me : map.entrySet()) { String key = me.getKey(); Integer value = me.getValue(); System.out.println("map集合中的key是:" + key + ",对应的value是:" + value); } } }
5 自定义泛型结构
5.1 泛型类和泛型接口
5.1.1 泛型的声明
- 语法:泛型类
class 类名<T>(){}
- 语法:泛型接口
interface 接口名<T1,T2>(){}
- 注:其中T、T1、T2表示任何引用类型,可以是任何字母表示。常用T表示,是Type的缩写。
5.1.2 泛型的实例化
- 一定要在类名后面指定类型参数的值(类型)。如下所示
List<String> list = new ArrayList<>();
- 泛型参数只能是类,不可以是基本数据类型,但可以使用包装类填充(包装类当然是引用数据类型^_^)。
- 把一个集合的内容限制为一个特定的数据类型,这就是泛型背后的核心思想。
5.1.3 泛型类、泛型接口的说明
- 泛型类可能有多个参数,此时应该将多个参数一起放在<>中。如:
class A<T1,T2,T3>{}
- 泛型类的构造器是没有泛型符号的,如:
public A(){}
- 实例化后,操作原来泛型位置的结构必须和指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。如:List<String>和List<Integer>是不能相互赋值的。
- 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
- 如果泛型结构是一个接口或者抽象类,则不可以创建类的对象。
- JDK1.7以后,泛型的简化操作:List<String> list = new ArrayList<>();
- 泛型的指定中不能使用基本数据类型,可以使用包装类。
- 在类、接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。因为加载时机不一致的。
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ public class Person<T> { private T t ; public Person() { } public Person(T t) { this.t = t; } public T getT() { return t; } public void setT(T t) { this.t = t; } /** * 静态方法中不能使用类的泛型,因为静态方法是优先于类执行的 */ // public static T getStaticT(){ // return t; // } }
- 异常类不能是泛型的(因为Exception等异常类不是泛型的)。
- 不能使用new E[]。但是可以使用:E[] elements = (E[]) new Object[capacity];
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ public class Father<T1,T2> { }
-
- 子类不保留父类的泛型:
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ //没有类型 擦除 public class Son extends Father { }
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ //有具体类型,即子类不是泛型类 public class Son extends Father<Integer,String> { }
-
- 子类保留父类泛型:
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ //全部保留 public class Son<T1,T2> extends Father<T1,T2> { }
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ //部分保留 public class Son<T2> extends Father<Integer,T2> { }
5.1.4 泛型方法
- 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。
- 在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
- 泛型方法的语法:
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) 抛出的异常{}
- 示例:
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ public class Dao { public <E> E get(int id,E e){ return null; } }
6 泛型在继承上的体现
- 如果B是A的一个子类型(子类或子接口),而G是具有泛型声明的类或接口,G<B>并不是G<A>的子类型。
- 如:String是Object的子类,但是List<String>并不是List<Object>的子类。
- 示例:
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ public class Person { }
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ public class Man extends Person { }
package day18; import java.util.List; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-23 */ public class CollectionTest { public static void main(String[] args) { Person[] people = null; Man[] man = null; //Person[]是Man[]的父类 people = man; List<Person> personList = null; List<Man> manList = null; //报错 // personList = manList; } }
7 在泛型中通配符
7.1 使用通配符
- 使用通配符<?>,例如List<?>,Map<?,?>。注意:List<?>是List<String>和List<Integer>的父类。
package day19; import java.util.ArrayList; import java.util.List; /** * @author xuweiwei * @version 1.0 * @since 2019-06-26 */ public class CollectionTest { public static void main(String[] args) { List<?> list = new ArrayList<>(); List<Object> objectList = new ArrayList<>(); List<String> stringList = new ArrayList<>(); List<Integer> integerList = new ArrayList<>(); list = objectList; list = stringList; list = integerList; } }
- 读取List<?>对象中List的元素时,永远是安全的,因为不管List中的元素的真实类型是什么,它包含的都是Object。
- 写入List<?>中的时候,除了null,其他类型的元素都不可以。
package day18; import java.util.ArrayList; import java.util.List; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class CollectionTest { public static void main(String[] args) { List<?> list = new ArrayList<>(); /** * 下面的写法是错误的。 * 在List集合中add方法的定义是add(E e)和List<E>中的E一致,需要我们传入特定的已知的类型。 * 而我们却传入的是?,?表明? extends Object,当然是未知类型了。 */ //list.add(1); } }
7.2 通配符的注意点
- 注意点:不能将通配符用在泛型方法声明处,返回值类型前面不能用<?>。比如:下面的写法是错误的。
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class CollectionTest { //错误的写法 /* public static <?> void add(List<?> list){ }*/ }
- 注意点:不能用在泛型类的声明上。比如:下面的写法是错误的。
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-24 */ //写法是错误的,不能用在泛型类上 public class Person<?> { }
- 注意点:不能用在创建集合对象上。
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class CollectionTest { public static void main(String[] args) { //写法错误,不能用在创建集合对象上 // List<?> list = new ArrayList<?>(); } }
7.3 有限制的通配符
- <?>允许所有泛型的引用调用。
package day18; import java.util.List; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class Person { public void printElement(List<?> list) { for (Object o : list) { System.out.println(o); } } }
package day18; import java.util.ArrayList; import java.util.List; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class CollectionTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); Person person = new Person(); person.printElement(list); } }
- 通配符指定上限:<? extends 类或接口>,使用指定的类型必须是继承某个类或者实现某个接口。即<=。
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class Person { }
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class Man extends Person { }
package day18; import java.util.List; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class CollectionTest { /** * * @param list 只能传入List集合中包含Person或其子类的元素 */ public static void printElement(List<? extends Person> list){ for (Person person : list) { System.out.println(person); } } public static void main(String[] args) { } }
- 通配符指定下限:<? super 类>,指定的类型不能小于操作的类。
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class Person { }
package day18; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class Man extends Person { }
package day18; import java.util.List; /** * @motto: 一身转战三千里,一剑可当百万兵。 * @author: 不为往事扰,余生只爱笑。 * @version: 1.0 * @since: 2019-06-27 */ public class CollectionTest { /** * * @param list 只能传入List集合中包含Person或其父类的元素 */ public static void printElement(List<? super Person> list){ for (Object o : list) { System.out.println(o); } } public static void main(String[] args) { } }