泛型
什么是泛型?
泛型即是标签,可以指定所需要的类型
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)
举例: 中药店,每个抽屉外面贴着标签 超市购物架上很多瓶子,每个瓶子装的是什么,有标签
泛型的设计背景 :
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,
JDK1.5之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,
因此此时把元素的类型设计成一个 参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E> 这个<E>就 是类型参数,即泛型。
泛型: 限制类型,只能放限制的类型,这样是一种安全限定,这样只放限制的类型,比较安全
理解泛型:
我们可以把泛型理解为一个个分类回收垃圾的垃圾桶,像垃圾分类 每个垃圾桶指定好收集的垃圾桶之后那么,
每个垃圾桶内只能放指定好的内容,这样在运送到垃圾回收站之后那么就不需要再垃圾回收站再去做复杂的分类操作
泛型是jdk1.5之后有的
从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念, 允许我们在创建集合时再指定集合元素的类型,正如:List<String>,这表明 该List只能保存字符串类型的对象。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持, 从而可以在声明集合变量、创建集合对象时传入类型实参。
为什么要有泛型?
那么为什么要有泛型呢,直接Object不是也可以存储数据吗? Object还可以当作任何类型的不好吗?
因为你把所有类型都当作Object的会出现不安全的现象,这样很多种类型都可以添加,没有了限制,不方便后续的使用
1. 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。 2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药 品都要辨别。
当集合没有泛型的时候
当集合有泛型的时候
泛型的使用:
我们用<>来作为泛型的限定 eg: List<Integer> list = new ArrayList<Integer>(); 也可以省略后半部的泛型 List<Integer> list = new ArrayList();
泛型不可以使用基础数据类型要使用基础数据类型与之对应的包装类
泛型在集合中的使用
当没有泛型的List传入不同类型的数据
@Test public void testOne(){ List list = new ArrayList(); for (int i = 0; i < 5 ; i++) { list.add(i); } // 因为没有泛型所以添加内容不安全 list.add("老王"); // 因为没有声明泛型所以默认为Object的可以传入String类型数据 for (Object obj:list ) { int score = (int)obj; // 因为有string类型所以要强转但是会出现ClassCastException 的错误因为不是同一个类型 System.out.println(score); } }
由上例可知没有声明泛型的List就可以传入不同类型的数据,当传入不同类型的数据时,在传入数据的时候会造成一股脑接收,在输出数据的时候会造成不必要的麻烦
当List使用泛型
@Test public void test1(){ List<Integer> list = new ArrayList<Integer>(); // 创建集合并且限定泛型只能传入int类型的参数 list.add(1); list.add(3); list.add(5); // list.add("你好"); // 因为已经限定了泛型为Integer所以不可以传入int之外的数据 for (int i:list ) { int score = i; // 声明了泛型也不用强转数据类型了因为都是同一类的了 System.out.println(score); } }
Map使用泛型
泛型还是可以嵌套的
@Test public void testTwo(){ Map<String,Integer> map = new HashMap<String, Integer>(); map.put("老王",134); map.put("隔壁老王",344); map.put("隔壁小姐姐",760); // map.put(123,"老刘"); // 这样是不行的因为声明了泛型就限定了传入的数据类型必须按照泛型来 Set<Map.Entry<String,Integer>> entrySet = map.entrySet(); // 泛型的嵌套 Iterator<Map.Entry<String,Integer>> iterator = entrySet.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next().getKey()); System.out.println(iterator.next().getValue()); } }
集合种使用泛型总结:
1: 集合结构或集合类在jdk5.0时都修改为带泛型结构 2: 在实例化集合类时,可以指明具体的泛型结构 3: 指明完之后在集合类或者接口类中凡是定义类或接口时,内部结构(如,方法 构造器,方法等)必须按照泛型的规定来 如:add(E e) --- >实例化后:add(Integer e) 4: 注意点:泛型饿类型必须是类,不能是基本数据类型,需要用到基本数据类型的位置,拿包装类替换 5: 如果实例化时没有指定泛型的类型,默认类型为:java.lang.Object类型
自定义泛型类
我们也可以在定义类的时候给类声明泛型
interface List<T> 和 class GenTest<K,V>
其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。 常用T表示,是Type的缩写。
T 可以理解为任意类型都可以相当于Object
泛型类的实例化
一定要在类名后面指定类型参数的值(类型)。 如: List<String> strList = new ArrayList<String>(); Iterator<Customer> iterator = customers.iterator(); T只能是类,不能用基本数据类型填充。但可以使用包装类填充
eg
Order类
public class JuneFourEveningOrder<T> { // T可以理解为Object 也可以用其它的大写字母代替 private String name; private int age; // 类的内部结构可以使用类的泛型 T orderT; public JuneFourEveningOrder(){}; public JuneFourEveningOrder(String name,int age,T orderT){ this.name = name; this.age = age; this.orderT = orderT; } public T getOrderT(){ return orderT; } public void setOrderT(T orderT){ this.orderT = orderT; } @Override public String toString() { return "JuneFourEveningOrder{" + "name='" + name + '\'' + ", age=" + age + ", orderT=" + orderT + '}'; } }
SubOrder类
public class JuneFourEveningSubOrder extends JuneFourEveningOrder<Integer> { // 继承父类的时候指明泛型类型 }
SubOrderOne类
public class JuneFourEveningSubOrderOne<T> extends JuneFourEveningOrder<T>{ }
public static void main(String[] args) { // 如果类定义了泛型类,实例化对象没有指明类的泛型,则认为此泛型类型为Object类型 // 因为类JuneFourEveningOrder定义了泛型所以实例化的对象中的泛型可以填写任意类型 // 如果在声明类的时候定义了泛型那么建议在实例化对象的时候也要指明泛型 JuneFourEveningOrder order = new JuneFourEveningOrder(); order.setOrderT(123); order.setOrderT("ABC"); // 建议 实例化对象的时候要指明泛型 JuneFourEveningOrder<String> orderOne = new JuneFourEveningOrder<String>("orderAA",15,"泛型"); orderOne.setOrderT("Hello"); // 因为对象实例化的时候指明泛型只能时String所以赋值的时候只能时String // 当子类继承父类时 父类指定了泛型类型那么子类的泛型类型也确定了 JuneFourEveningSubOrder subOrder = new JuneFourEveningSubOrder(); // 由于子类在继承带泛型的父类时,指明了泛型类型,则实例化子类对象时,不再需要指明泛型 subOrder.setOrderT(123); JuneFourEveningSubOrderOne<String> one = new JuneFourEveningSubOrderOne<String>(); one.setOrderT("老王"); }
Summary:
1: 如果类定义了泛型类,实例化对象没有指明类的泛型,则认为此泛型类型为Object类型 2: 如果在声明类的时候定义了泛型那么建议在实例化对象的时候也要指明泛型 3: 当子类继承父类时 父类指定了泛型类型那么子类的泛型类型也确定了 4: 由于子类在继承带泛型的父类时,指明了泛型类型,则实例化子类对象时,不再需要指明泛型
泛型不同的引用不能相互赋值
Person one = null; Person two = null; 所以one = two; 是可以的 但是 List <Integer> listOne = new ArrayList(); List<String> listTwo = new ArrayList(); listOne = ListTwo是可以的 因为泛型的类型不同,因为不同类型的添加的值也不同所以 不可以 如果可以那就是String类型的泛型可以添加int了所以是不成立的
泛型方法:
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型 方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型
泛型方法的格式
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常 eg: public <E> E getValue(){ //定义一个泛型方法 return null; } public <E>List<E> copyFromArrayList(E [] arr){ // 定义一个方法 传入的是E类型的数组,返回的是类型的List ArrayList<E> arrayList = new ArrayList<>(); for (E e:arr ) { arrayList.add(e); } return arrayList; }
泛型方法格式二:
权限修饰符 返回值类型和泛型一起 方法名(泛型参数){}
权限修饰符 带泛型的返回值类型 方法名(泛型参数){} eg: public List<String> getValue(HashMap<String,String> hashMap){} 定义一个返回值时List<string>的泛型list请求参数时map<String,String>泛型
// 泛型方法: 在方法中出现了泛型结构,泛型参数与类的泛型参数没有任何关系 // 换句话说,泛型方法所属的类是不是泛型类都没有关系 //泛型方法可以声明为static,因为泛型方法的泛型参数时在调用的时候确定的,并非在实例化的时候确定的
泛型方法所属的类是不是泛型类都可以 ,泛型类可以其它泛型或者不是泛型 泛型方法可以是不同的泛型
eg:
JuneFourEveningOrder类 public <E>List<E> copyFromArrayList(E [] arr){ // 定义一个方法 传入的是E类型的数组,返回的是类型的List ArrayList<E> arrayList = new ArrayList<>(); for (E e:arr ) { arrayList.add(e); } return arrayList; } 运行 @Test public void TestOne(){ JuneFourEveningOrder<String> order = new JuneFourEveningOrder(); // 声明泛型类是String的泛型 Integer [] array = new Integer []{1,2,3,4}; // 泛型方法在调用时,指明泛型参数的类型 List<Integer> list = order.copyFromArrayList(array); // 可以看出类的泛型时String而方法的时Integer这样时可以的这就是泛型方法 System.out.println(list); }
同一个类中的普通方法的泛型和 对象锁指定的泛型方法无关,set方法中的泛型和对象所指定的泛型相同
class JuneSixTestOne<T>{ private T orderid; private int id; private T List; public void setOrderi(T Orderid){ // this.orderid = orderid; } public <T>T poi(){ return null; } public <T> String getStr(T num){ return null; } public JuneSixTestOne(T orderid, int id) { this.orderid = orderid; this.id = id; } public <T> List forList(T [] List){ List<T> listOne = new ArrayList(); for (T t: List ) { listOne.add(t); } return listOne; } }
public static void main(String[] args) { JuneSixTestOne<String> one = new JuneSixTestOne<String>("老王",13); // 普通的泛型方法中的泛型不与对象指定的泛型有关 String str = one.getStr("laowang"); String str1 = one.getStr(123); // 虽然对象指定了泛型T为String泛型方法中可以传入任意的因为非set的泛型方法不与对象指定的泛型有关 System.out.println(str1); System.out.println(one.getStr(123)); // set方法的泛型与对象指定的泛型相关 // one.setOrderi(123); 报错提供的是int 需要的是String one.setOrderi("123"); // 因为T在 // 再一次验证普通方法的泛型并不和对象指定的泛型有关 //此时对象的泛型T是String 而此方法的泛型T是Integer是由他自己本身指定的 System.out.println(one.forList(new Integer[]{1, 2, 3, 4})); // [1, 2, 3, 4] }
验证set方法中的泛型与对象所指定的泛型相关
// set方法的泛型与对象指定的泛型相关 // one.setOrderi(123); 报错提供的是int 需要的是String one.setOrderi("123"); // 因为T在对象中的指定是String这个时候set方法中的泛型也必须是String 因为set方法中的泛型需和对象中的相同
验证类中的普通方法与对象中的泛型无关
普通的泛型方法中的泛型不与对象指定的泛型有关 String str = one.getStr("laowang"); String str1 = one.getStr(123); // 虽然对象指定了泛型T为String泛型方法中可以传入任意的因为非set的泛型方法不与对象指定的泛型有关 System.out.println(str1); // 再一次验证普通方法的泛型并不和对象指定的泛型有关 //此时对象的泛型T是String 而此方法的泛型T是Integer是由他自己本身指定的 System.out.println(one.forList(new Integer[]{1, 2, 3, 4})); // [1, 2, 3, 4]
由上可知,虽然类的泛型是T方法中也使用了T泛型,在实例化时对象指定了泛型为String类型,但是只有set方法中的泛型必须按照对象实例化是指定的泛型来规划,其它方法中的泛可以和对象指定的泛型不同,
自定义泛型结构:泛型类、泛型接口Summary
1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如: <E1,E2,E3> 2. 泛型类的构造器如下:public GenericClass(){}。 而下面是错误的:public GenericClass<E>(){} 3 . 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。 4. 泛型不同的引用不能相互赋值。 >尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有 一个ArrayList被加载到JVM中。 5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价 于Object。经验:泛型要使用一路都用。要不用,一路都不要用。 6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。 7. jdk1.7,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>(); 8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。 9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法 中不能使用类的泛型。 10. 异常类不能是泛型的 11. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]; 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。 12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
没有类型 擦除 具体类型
子类保留父类的泛型:泛型子类
全部保留 部分保留 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型
泛型在继承中的体现:
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的 类或接口,G<B>并不是G<A>的子类型
比如:String是Object的子类,但是List<String >并不是List<Object> 的子类。
举例:
/* 类A是类B的父类,那么G<A> 和G<B>是不具备子父类关系的,二者是并列关系
类A是类B的父类 那么A<G> 和B<G>是字父类关系 */ @Test public void TestTwo(){ Object one = null; Object two = null; one = two; // 这种是成立的 这是多态 子类的对象赋予父类的引用 Object [] arrOne = null; String [] arrTwo = null; arrOne = arrTwo; // 成立 也是多态的展示 // 此时listOne 和ListTwo 的类型不具备子父类关系虽然String是Object确实具备子父类关系但是List类本身不具备子父类关系 List<Object> listOne = null; List<String> listTwo = null; // listOne = listTwo; // 这是不可行的, /* 相当于 Date date = new Date(); str = date; 这种 因为两个类型不同 反证法 如果listOne = listTwo ; 那么既可以添加 listOne.add(String); 又可以list.add(int); 那就违反了泛型的限定所以不可以 */ }
类A是类B的父类 那么A<G> 和B<G>是子父类关系
因为AbstractList是ArrayList的父类所以下面的成立 AbstractList<String> listThree = null; ArrayList<String> listFour = null; listThree = listFour;
泛型的通配符
使用类型通配符:?
比如:List<?> ,Map<?,?> List<?>是List<String>、List<Object>等各种泛型List的父类。
List<?>的注意点
2.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型 是什么,它包含的都是Object 3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中 添加对象。 唯一的例外是null,它是所有类型的成员。 将任意元素加入到其中不是类型安全的: Collection<?> c = new ArrayList<String>(); c.add(new Object()); // 编译时错误 因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集 合的元素类型。
我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知 道那是什么类型,所以我们无法传任何东西进去。 唯一的例外的是null,它是所有类型的成员。 另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的 类型,但是我们知道,它总是一个Object。
?通配符不可以使用点
//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用? public static <?> void test(ArrayList<?> list){ } //注意点2:编译错误:不能用在泛型类的声明上 class GenericTypeClass<?>{ } //注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象 ArrayList<?> list2 = new ArrayList<?>();
有限制的通配符的使用
<?> 允许所有泛型的引用调用 通配符指定上限 上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<= 无穷小 通配符指定下限 下限super:使用时指定的类型不能小于操作的类,即>= 无穷大 eg: <? extends Number> (无穷小 , Number] 只允许泛型为Number及Number子类的引用调用 <? super Number> [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用 <? extends Comparable> 只允许泛型为实现Comparable接口的实现类的引用调用
List<?>的使用详解
eg:
建立两个类: 父类 Person 和子类: Student类
@Test public void TestOne() { // List<?>是List<string> 和List<Object>等各种泛型的父类 List<?> list = null; List<JuneEightForenoonPerson> listThree = null; List<JuneEightForenoonStudent> listFour = null; list = listThree; list = listFour; List<String> listFive = new ArrayList<>(); listFive.add("AA"); listFive.add("BB"); list = listFive; /* 添加元素 List<?>内不可以添加除了null之外的数据,如果可以添加其它元素那么就是介意添加String又可以添加int 就违反了泛型的限定,只能添加一种的限定又回到了List<String>= List<int>了这是行不通的 */ // list.add("123"); 报错 //list.add(123); 行不通不可以添加除了null之外的数据 list.add(null); //get读取数据是可以的因为是可以以所有数据的根父类Object来接收 Object objOne = list.get(0); System.out.println(objOne); Iterator<?> iter = list.iterator(); while (iter.hasNext()){ Object obj = iter.next(); // 因为是不确定的值我们可以使用所有的根父类Object来获取值 System.out.println(obj); } }
添加元素:
List<?>内不可以添加除了null之外的数据,如果可以添加其它元素那么就是介意添加String又可以添加int 就违反了泛型的限定,只能添加一种的限定又回到了List<String>= List<int>了这是行不通的
取数据:
get读取数据是可以的因为是可以以所有数据的根父类Object来接收
List<?extends 类> 与List<? super 类>的使用详解
eg:
@Test public void TestTwo(){ List<? extends JuneEightForenoonPerson> listOne = null; List<? super JuneEightForenoonPerson> listTwo = null; List<Object> listThree = null; List<JuneEightForenoonPerson> listFour = null; List<JuneEightForenoonStudent> listFive = null; // 赋值 // ? extends Person 是指 <= Person类的 这个时候 Object是大于Person类的而Person和Student是满足的 //listOne = listThree; listOne = listFour; listOne = listFive; // ? super Person 是指 >= Person类的 而Object是所有类的根父类是满足的Person本身也满足 而它的子类Student是不满足的 listTwo = listFour; // listTwo = listFive; listTwo = listThree; // 添加数据 // listOne 因为是小于等于Person的 它有一个小于 如果是小于的它子类Student的话你在指定必须传student类也是报错,所以不可以传递任何传递任何元素 //listOne.add(new JuneEightForenoonStudent()); //listOne.add(new JuneEightForenoonPerson()); // ? super Person 是可以添加它的子父类的元素对象都可以的 因为可以接收比父类无穷大的元素 listTwo.add(new JuneEightForenoonPerson()); listTwo.add(new JuneEightForenoonStudent()); //获取数据 JuneEightForenoonPerson person = listOne.get(0); // 因为是小于等于Person 我们要拿最大的类型来获取 // JuneEightForenoonStudent student = listOne.get(0); }
.
.