集合泛型

 

 

一、集合概述

(一)体系结构

1、集合分类:

  单列集合:每个元素都是一个单独的个体

  双列集合:每个操作都是针对一对数据来进行的,一对数据作为一个单位

2、单列集合的体系:

  Collection 单列集合的顶层接口

    List 有序的子接口

      ArrayList     顺序存储,查询修改块

      LinkedList 链式存储,增加删除块

    Set 无序的子接口

      HashSet 哈希存储

        LinkedHashSet

3、双列结合的体系:

  Map 双列集合的顶层接口

    HashMap       哈希表存储

      LinkedHashMap

 

二、Collection

(一)概述和常用方法

1、单词:收集、集合

2、单列集合的顶层接口,定义的是所有单列集合中共有的功能。

3、Collection是一个接口,不能直接创建对象,随意找一个实现类创建对象

  使用接口类型的引用,指向实现类的对象

  Collection类型的引用,指向 ArrayList类型的对象(只能调用接口中的方法)

4、常用方法:

  add(Object obj):将obj元素添加到集合中

  remove(Object obj):将obj元素从集合中删除

  clear():将集合中的元素清空

  isEmpty():判断集合是否为空

  contains(Object obj):判断集合中是否包含obj元素

size():返回集合中的元素个数

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.Collection; public class Demo01_Collection概述和常用方法 { public static void main(String[] args) { Collection list = new ArrayList(); list.add("abc"); list.add("abc"); list.add(123); list.add('中'); list.add(123); list.add(3.14); list.add(3.14); list.add('中'); list.add(true); list.add(true); System.out.println(list.contains("abc")); System.out.println(list.size()); /* * System.out.println(list); * * list.remove(123); * * System.out.println(list); * * System.out.println(list.isEmpty()); * * list.clear(); * * System.out.println(list.isEmpty()); System.out.println(list); */ } }

 

(二)Collection的第一种遍历方式

1、转成数组,通过遍历数组的方式,来间接的遍历集合

2、Object[] toArray():将调用者集合转成Object类型的数组

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.Collection; public class Demo02_Collection的第一种遍历方式 { public static void main(String[] args) { Collection list = new ArrayList(); list.add("abc"); list.add(123); list.add(123); list.add(66); list.add(-88); list.add(-88); list.add('9'); list.add('9'); list.add(false); list.add(3.14); list.add(3.14); //调用toArray()方法,将集合中的元素添加到Object[]数组中,之后遍历数组 Object[] arr = list.toArray(); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } }

 

(三)Collection中带all的方法

1、addAll(Collection c):将参数c中的所有元素,都添加到调用者集合中

2、removeAll(Collection c):从调用者集合中,删除那些也存在于参数c中的元素

3、containsAll(Collection c):判断调用者,是否能包含参数c中的所有元素

4、retainAll(Collection c):参数c中有哪些元素,就在调用者集合中,保留哪些元素(交集)

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.Collection; public class Demo03_Collection中带All的方法 { public static void main(String[] args) { Collection list1 = new ArrayList(); list1.add("abc"); list1.add(123); list1.add(123); list1.add(66); list1.add(-88); list1.add(-88); list1.add('9'); list1.add('9'); list1.add(false); list1.add(3.14); list1.add(3.14); Collection list2 = new ArrayList(); list2.add("abc"); System.out.println(list1); list1.retainAll(list2); System.out.println(list1); /*list2.add("把液喝烈"); list2.add("无情哈拉少"); list2.add("欧青辣少");*/ /*System.out.println(list1); list1.addAll(list2); System.out.println(list1); list1.removeAll(list2); System.out.println(list1);*/ //containsAll(Collection<?> c) 判断参数集合是否是调用者集合的子集 //System.out.println(list1.containsAll(list2)); } }

 

(四)集合遍历的第二种方式:迭代器

1、迭代:更迭、更新换代,有从某一个到下一个的过程的含义

2、迭代器:专门用于将集合中的元素,一个到另一个逐个进行迭代的对象

3、获取:集合自己内部就应该有一个可以迭代自己的对象,从集合对象中获取即可

  Iterator  iterator()

4、迭代器的使用:

  方法iterator返回的是一个Iterator接口的实现类对象,可以使用的就是Iterator接口中的方法:

  hasNext():判断集合中是否还有下一个元素

  next():获取集合中的下一个元素

  remove():删除迭代器对象正在迭代的那个对象

5、迭代器的注意事项:

  (1)迭代器对象虽然多次调用next方法,都是同样的方法名称,但是每次调用返回的结果是不同的,会因为next方法既可以获取下一个元素,也会让迭代器对象,向前移动一步。

  (2)如果没有下一个元素,仍然调用next方法,出现NoSuchElementException(没有当前元素异常),可以使用hasNext方法判断是否有下一个元素,决定是否调用next方法

  (3)hasNext方法,判断是否有下一个元素,不会移动迭代器的位置

  (4)next方法,不仅可以获取下一个元素,还会移动迭代器位置

  (5)不要只判断一次hasNext,就调用多次next方法

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Demo04_集合的第二种遍历方式_迭代器 { /** * 注意事项:迭代器遍历,调用一次hasNext()方法,只能够调用一次next()方法, * 如果调用一次hasNext()方法搭配多次next()方法,容易出现NoSuchElementException */ public static void main(String[] args) { Collection list = new ArrayList(); list.add(new Person("zhangsan", 23)); list.add(new Person("lisi", 24)); list.add(new Person("wangwu", 25)); //list.add(new Person("zhaoliu", 26)); //1.获取迭代器对象 Iterator it = list.iterator(); //2.迭代器遍历 while(it.hasNext()) { Object obj = it.next(); Person p = (Person)obj; System.out.println(p.getName() + "..." + p.getAge()); //System.out.println(((Person)it.next()).getName() + "..." + ((Person)it.next()).getAge()); } } public static void test() { /* * 迭代器,集合特有的遍历方式 * 通过迭代器来遍历集合,所有的遍历操作是迭代器来完成的! * * */ Collection list = new ArrayList(); list.add("abc"); list.add(123); list.add(123); list.add(66); list.add(-88); list.add(-88); list.add('9'); list.add('9'); list.add(false); list.add(3.14); list.add(3.14); //1.调用Collection中的iterator()方法,获取当前集合的迭代器对象 Iterator it = list.iterator(); //2.遍历操作全都是由迭代器来完成 //hasNext()方法仅仅是判断是否还有下一个元素 //next()具体获取一个元素 while(it.hasNext()) { System.out.println(it.next()); } } }

 

三、List

(一)概述

1、是Collection的一个子接口

2、特点:

  有序:每个元素都有自己的位置,不同位置是有分别的

  【有索引】:每个元素都有自己的编号

  可以重复:即使是值相同的几个元素,位置和索引也各不相同,可以区分这几个值。

3、特有方法:

  add(int index, Object obj):在指定索引上,添加指定的元素

  remove(int index):删除指定索引上的值

  set(int index, Object obj):将指定索引上的值,修改为指定的值

  get(int index):根据给定的索引,获取对应位置的值

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.List; public class Demo05_List概述 { public static void main(String[] args) { List list = new ArrayList(); list.add(111); list.add(111); list.add(222); list.add(222); list.add(333); list.add(333); System.out.println(list); //add(int index, E element) list.add(2, 666); System.out.println(list); //remove(int index)删除指定位置的元素并且返回这个元素 System.out.println(list.remove(list.size() - 1)); System.out.println(list); //set(int index, E element)修改指定位置为新的元素,将原来的元素进行返回 System.out.println(list.set(0, 999)); System.out.println(list); System.out.println(list.get(3)); } }

 

(二)第三种遍历方式

1、针对List集合特有的遍历方式

2、可以通过集合的size方法获取list集合索引的范围,根据索引通过get方法可以获取指定索引的值。

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.List; public class Demo06_集合的第三种遍历方式 { /* * 这种方式最多用于List接口的引用指向实现类的对象,不能是 * Collection接口的引用指向实现类对象 * * 因为get方法属于List接口,Collection中没有 * * */ public static void main(String[] args) { List list = new ArrayList(); list.add(111); list.add(111); list.add(222); list.add(222); list.add(333); list.add(333); //get(int index) //索引从0开始到size() - 1 结束 for(int i = 0; i <= list.size() - 1; i++) { System.out.println(list.get(i)); } } }

 

(三)并发修改异常

1、ConcurrentModificationException

  并发       修改      异常

2、出现原因:

  在使用【迭代器对象】遍历集合的同时,使用【集合对象】增加集合的元素

3、避免方法:两种避免方式都是针对List集合

  方式1:迭代器遍历、迭代器增加

  方式2:集合遍历、集合增加

4、避免方式1:迭代器遍历、迭代器增加

  迭代器遍历,和以前一样

  迭代器增加:问题普通的迭代器中没有增加的方法,需要使用List中特有的迭代器

  列表迭代器:ListIterator,是Iterator的子接口,拥有Iterator中的所有方法,还有特有

  列表迭代器的获取:listIterator()

5、避免方式2:集合遍历、集合添加

  集合遍历:list特有的方式遍历,size和get方法结合

  集合添加:集合的add方法

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class Demo07_并发修改异常 { public static void main(String[] args) { List list = new ArrayList(); list.add("abc"); list.add("qwe"); list.add("abc"); list.add("zzz"); list.add("bnm"); list.add("bnm"); list.add("iop"); /* * 迭代器遍历集合 * 当遇到元素"zzz"时 * 向集合中插入"666" * * ConcurrentModificationException * 并发 修改 异常 * * 产生原因:迭代器遍历集合的同时,集合对象进行修改 * 解决办法: * ①集合遍历集合修改 * ②迭代器遍历,迭代器修改 listIterator()是List集合特有的迭代器 * */ ListIterator lit = list.listIterator(); while(lit.hasNext()) { if("zzz".equals(lit.next())) { lit.add("666"); } } System.out.println(list); } public static void coll(List list) { for (int i = 0; i < list.size(); i++) { if("zzz".equals(list.get(i))) { list.add("666"); } } System.out.println(list); } public static void question(List list) { Iterator it = list.iterator(); while(it.hasNext()) { Object obj = it.next(); if("zzz".equals(obj)) { list.add("666"); } } } }

 

四、List的实现类

(一)概述

1、List只是一个接口,根据底层实现方式的不同,具有不同的实现类

2、ArrayList:数组实现,顺序存储

3、LinkedList:节点实现,链式存储

 

(二)ArrayList

1、也是List的一个实现类

2、没有什么特有方法

3、存储方式:

  数组实现,顺序存储

  通过物理内存的位置关系,来表达描述逻辑顺序的相邻

4、图示:

 

(三)LinkedList

1、List的一个实现类

2、存储方式:

  节点实现,链式存储

  不通过物理内存位置的相邻,来表示逻辑顺序的相邻

  每个元素都存储在一个节点中,节点除了元素数据本身以外,还需要存储下一个元素的内存地址

3、图示:

 

4、特点:

  查询速度慢:需要根据前面的节点来获取后一个节点的地址,前面所有节点都要访问一遍,节点数量越多,查询虚度越慢

  增删速度快:增删一个元素,只需要修改新增元素前后的两个节点的引用域即可,与集合本身的元素个数无关。

5、LinkedList的特有方法:

  由于在LinkedList中,维护了链表的头和尾节点的对象地址,所以操作头部和尾部非常容易,提供了大量的操作头和尾的方法。

  addFirst(Object obj):在头部添加元素

  addLast(Object obj):在尾部添加元素

  removeFirst():删除头部元素

  removeLast():删除尾部元素

  getFirst():获取头部元素

  getLast():获取尾部元素

代码示例

package com.offcn.demos; import java.util.LinkedList; public class Demo08_LinkedList { public static void main(String[] args) { LinkedList list = new LinkedList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); list.add("eee"); //System.out.println(list); /*list.addFirst(null); list.addLast("111");*/ /*System.out.println(list.removeFirst()); System.out.println(list.removeLast());*/ System.out.println(list.getFirst()); System.out.println(list.getLast()); //System.out.println(list); } }

 

(四)练习1

比较ArrayList和LinkedList在头部增删元素的效率

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.LinkedList; /** *比较ArrayList和LinkedList在头部增删元素的效率 * */ public class Demo09_练习1 { public static final int NUM = 180000; public static void main(String[] args) { /*test1(); test2();*/ test3(); test4(); } public static void test4() { LinkedList list = new LinkedList(); for (int i = 0; i < NUM; i++) { list.addFirst(i); } long start = System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { list.removeFirst(); } long end = System.currentTimeMillis(); System.out.println("LinkedList头部删:" + (end - start)); } public static void test3() { ArrayList list = new ArrayList(); for (int i = 0; i < NUM; i++) { list.add(0, i); } long start = System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { list.remove(0); } long end = System.currentTimeMillis(); System.out.println("ArrayList头部删:" + (end - start)); } public static void test2() { LinkedList list = new LinkedList(); long start = System.currentTimeMillis(); for (int i = 0; i < NUM; i++) { list.addFirst(i); } long end = System.currentTimeMillis(); System.out.println("LinkedList头部增:" + (end - start)); } public static void test1() { ArrayList list = new ArrayList(); long start = System.currentTimeMillis(); for (int i = 0; i < NUM; i++) { list.add(0, i); } long end = System.currentTimeMillis(); System.out.println("ArrayList头部增:" + (end - start)); } }

 

(五)练习2

比较ArrayList和LinkedList查询元素的效率

代码示例

package com.offcn.demos; import java.util.ArrayList; import java.util.LinkedList; public class Demo10_练习2 { public static final int NUM = 150000; public static void main(String[] args) { test1(); test2(); } public static void test2() { LinkedList list = new LinkedList(); for (int i = 0; i < NUM; i++) { list.addFirst(i); } long start = System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { list.get(i); } long end = System.currentTimeMillis(); System.out.println("LinkedList查询:" + (end - start)); } public static void test1() { ArrayList list = new ArrayList(); for (int i = 0; i < NUM; i++) { list.add(0, i); } long start = System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { list.get(i); } long end = System.currentTimeMillis(); System.out.println("ArrayList查询:" + (end - start)); } }

 

五、泛型

(一)泛型的概述和使用【熟练掌握】

1、泛型:广泛的类型,在定义一个类的时候,类型中有些方法参数、返回值类型不确定,就使用一个符号,来表示那些尚未确定的类型,这个符号,就称为泛型。

2、使用:对于有泛型的类型,在这些类型后面跟上尖括号,尖括号里面写上泛型的确定类型(在使用某个类创建对象时,已经可以确定这个具体的类型了,那么就直接写出具体类型)

  例如:ArrayList<Integer> al = new ArrayList<Integer>();

3、泛型的好处:

  (1)提高了数据的安全性,将运行时的问题,提前暴露在编译时期

  (2)避免了强转的麻烦

4、注意事项:

  (1)前后一致:在创建对象时,赋值符号前面和后面的类型的泛型,必须一致

  (2)泛型推断:如果前面的引用所属的类型已经写好了泛型,后面创建对象的类型就可以只写一个尖括号,尖括号中可以不写任何内容。<>特别像菱形,称为“菱形泛型”,jdk1.7特性

代码示例1

package com.offcn.demos; import java.util.ArrayList; public class Demo11_泛型之没有泛型 { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("abc"); list.add("def"); list.add("ghi"); list.add("klm"); list.add(666); /* * 获取集合中的元素,拆分成字符数组 * * 缺点:1.需要向下转型,比较麻烦 * 2.在转型时候,如果有其他类型,会存在隐含的错误,在运行期才能被发觉 * */ for (int i = 0; i < list.size(); i++) { Object obj = list.get(i); String str = (String)obj; char[] arr = str.toCharArray(); } } }

代码示例2

package com.offcn.demos; import java.util.ArrayList; public class Demo12_泛型之有泛型 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); list.add("abc"); list.add("def"); list.add("ghi"); list.add("klm"); //list.add(666); /* * 加入泛型:1.将运行期的问题提前暴露在编译期,让程序员尽早发现问题 * 2.省去了向下转型的麻烦 * * */ for (int i = 0; i < list.size(); i++) { String str = list.get(i); } } }

 

(二)泛型类的定义

1、泛型类:带着泛型定义的类

2、格式:

  class 类名<泛型类型1, 泛型类型2, .....>  {

 

}

3、说明:

  (1)类名后面跟着的泛型类型,是泛型的声明,一旦泛型声明出来,就相当于这个类型成为了已知类型,这个类型就可以在整个类中使用

  (2)泛型的声明名称,只需要是一个合法的标识符即可,但是通常我们使用单个大写字母来表示,常用字母:

E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number(数值类型) ? -  表示不确定的java类型

  (3)泛型确定的时机:将来在使用和这个类,创建对象的时候

代码示例

package com.offcn.demos; public class Demo13_泛型类的定义 { public static void main(String[] args) { /* * 1.泛型只能写成引用数据类型 * 2.泛型本身,例如:<T>并不会代表实际上的任何类型,只是告诉外界,此处可以有一个数据类型 * 3.泛型确定的时机:对象创建出来的时候,泛型必须确定为具体的类型, * 类的泛型也就随即确定成了具体的类型,所有使用了类泛型的类成员,数据类型也就随之确定下来 * * */ Animal<String> a = new Animal<>(); a.print("abc"); Animal<Integer> a1 = new Animal<>(); a1.print(123); Animal<Character> a2 = new Animal<>(); a2.print('z'); } } /* * 定义:带有泛型的类就是泛型类 * * 书写格式: * class 类名<泛型1, 泛型2, 泛型3...> { * * } * * 泛型类在定义的时候,泛型表示的是不确定的类型,只需要使用大写字母来定义即可 */ class Animal<T> { public void print(T t) { System.out.println(t); } /*public void print(String str) { System.out.println(str); }*/ /*public void print(Integer in) { System.out.println(in); }*/ }

 

(三)泛型类练习

定义一个带泛型的集合,只能在集合的头部进行增删

代码示例

package com.offcn.demos; import java.util.LinkedList; //定义一个带泛型的集合,只能在集合的头部进行增删 public class Demo14_泛型练习1 { public static void main(String[] args) { MyStack<String> ms = new MyStack<>(); ms.push("abc"); ms.push("def"); ms.push("ghi"); while (!ms.empty()) { System.out.println(ms.pop()); } } } //栈:先进后出,后进先出 class MyStack<T> { private LinkedList<T> ll = new LinkedList<>(); //进栈 public void push(T t) { ll.addFirst(t); } //出栈 public T pop() { return ll.removeFirst(); } public boolean empty() { return ll.isEmpty(); } }

 

(四)泛型方法的定义

1、在方法声明中,带着泛型声明的方法,就是泛型方法

2、格式:

  修饰符 <泛型声明1, 泛型声明2,.....> 返回值类型 方法名称(参数列表) {

 

}

3、说明:

  (1)在方法上声明的泛型,可以在整个方法中,当做已知类型来使用

  (2)如果【非静态】方法上没有任何泛型的声明,那么可以使用类中定义的泛型

  (3)如果【静态】方法上没有任何的泛型声明,那么就不能使用泛型,连类中定义的泛型,也不能使用,因为类中的泛型需要在创建对象的时候才能确定。所以【静态】方法想使用泛型,就必须在自己的方法上单独声明。

代码示例

package com.offcn.demos; public class Demo15_泛型方法的定义 { public static void main(String[] args) { MethodTest<String> mt = new MethodTest<>(); mt.test1("abc"); mt.test3(123); mt.test3("abc"); mt.test3(3.14); } } /* * 权限修饰符 【static】 返回值类型 方法名称(形参列表) { * 方法体语句; * } * * 非静态方法: * 1.可以使用类的泛型:此时泛型的确定,随着对象泛型类型的确定而确定 * 2.可以自己定义自己的泛型:传什么类型的数据,就确定为什么类型 * 权限修饰符 <泛型1, 泛型2, 泛型3...> 返回值类型 方法名称(形参列表) { * 方法体语句; * } * * 静态方法: * 静态方法不能使用类的泛型,如果想要使用泛型必须自己定义泛型 * 因为:类的泛型确定时机是在对象创建出来的时候,可静态优先对象存在,所以静态只能自己定义泛型自己使用 * 权限修饰符 static <泛型1, 泛型2, 泛型3...> 返回值类型 方法名称(形参列表) { * 方法体语句; * } * * */ class MethodTest<T> { public void test1(T t) { System.out.println(t); } public T test2() { return null; } public <E> void test3(E e) { System.out.println(e); } public static <T> void test4(T t) { } }

 

(五)泛型方法练习

定义一个方法,可以交换任意类型的数组的某两个元素

代码示例

package com.offcn.demos; //定义一个方法,可以交换任意类型的数组的某两个元素 public class Demo17_泛型方法练习 { public static void main(String[] args) { /*String[] arr = { "abc", "qwe", "www" }; swap(arr, 0, 2); System.out.println(Arrays.toString(arr));*/ Integer[] arr = {1, 23, 44, -9}; swap(arr, 0, 3); } public static <E> void swap(E[] arr, int index1, int index2) { E temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; } }

 

(六)泛型接口的定义和使用

1、带着泛型定义的接口,就是泛型接口

2、定义格式:

  interface 接口名称<泛型类型1, 泛型类型2,...> {

 

}

3、说明:

  1、在接口声明上,定义好的泛型,可以在整个接口中当做已知类型来使用

  2、泛型接口被其他类实现的时候,有两种实现方式:  

    1、声明的类不再是一个泛型类,而是一个确定了泛型的类,格式如下:      

class 实现类类名 implements 接口名<具体类型> {

        所有的泛型类型都已经被确定

}

    2、声明的来还是一个泛型类,泛型的类型和接口的泛型一致,格式如下:

      class 实现类类名<泛型标识符1> implements 接口名<泛型标识符1> {

       所有的方法还都是可以使用泛型标识符的方法。

}

4、集合体系中,大量的接口和实现类都存在实现和继承的关系

  Collection<E>...继承关系...List<E>....实现关系....ArrayList<E>

备注:ctrl + shift + t 搜索 输入名称后可查看原码

代码示例

package com.offcn.demos; public class Demo16_泛型接口的定义和使用 { } /* * * interface 接口名称<泛型1, 泛型2, 泛型3...> { * * } * * 1.当类实现接口的时候,接口的泛型确定了,此时类实现抽像方法的时候,所有使用了接口泛型的方法的泛型,也随之确定 * 2.当类实现接口时,接口带有的泛型依然没有确定,此时所有使用了接口泛型的方法,其泛型依然不会被确定 */ interface MyInter<T> { public abstract void test1(T t); public abstract T test2(); } /* * class 类名<泛型1, 泛型2, 泛型3...> implements 接口名<泛型1, 泛型2, 泛型3...> { * * } * * 注意:1.接口原来的泛型用符号T,被实现的时候用符号E,其实没有任何关系,就表示接口带有一个或者几个泛型而已 * 2.实现类所带有的泛型符号必须和接口的泛型符号保持完全一致 * */ class Inter2<E> implements MyInter<E> { @Override public void test1(E t) { } @Override public E test2() { return null; } } /* * class 类名 implements 接口名<具体的类型> { * * } * * */ class Inter1 implements MyInter<String> { @Override public void test1(String t) { } @Override public String test2() { return null; } }

 

(七)泛型的通配符(了解)

1、使用泛型的时候,没有使用具体的泛型声明T,而是使用了和声明过的某个泛型T有关的一类类型,就称为泛型的通配符。三种形式:

2、第一种形式,使用?来表示可以是任意类型,例如:

  Collection<E>接口中的removeAll(Collection<?> c),表示可以接收任意泛型类型的集合,作为该方法的实际参数,参数集合的泛型,可以是与E没有任何关系

//removeAll(Collection<?> c) 从调用者集合中删除指定集合 //此处的?:参数集合的泛型可以和调用者对象的泛型没有任何关系 Collection<String> list = new ArrayList<>(); list.add("abc"); Collection<Integer> list1 = new ArrayList<>(); list1.add(123); System.out.println(list); list.removeAll(list1); System.out.println(list);

 

3、第二种形式,使用? extends E来表示必须是某个泛型类型或是该泛型类型的子类,例如:

  Collection<E>接口中的addAll(Collection<? extends E> c),表示可以接收泛型类型是调用者泛型类型或者其子类的集合,作为该方法的实际参数。参数的泛型和调用者的泛型,必须有关(相同或者是子父类)。确定了泛型的上边界。

//addAll(Collection<? extends E> c) 此处参数集合的泛型必须是调用者集合泛型本类或者子类 //? extends E 确定泛型的上边界 Collection<Object> list1 = new ArrayList<>(); Collection<String> list2 = new ArrayList<>(); Collection<Object> list3 = new ArrayList<>(); list1.addAll(list2); //list2.addAll(list1); list1.addAll(list3);

 

4、第三种形式,使用? super E来表示必须是某个泛型类型或者是该泛型类型的父类,例如:

  Arrays工具类中,排序方法static <T> void sort(T[] a, Comparator<? super T> c),T是该方法的泛型,T表示的是数组中元素的类型,<? super T>表示可以接收泛型类型是数组元素类型T或者是元素类型T的父类的比较器,作为sort方法的参数。参数的泛型和方法的泛型,必须有关(相同或者是子父类)。确定了泛型的下边界。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值