文章目录
一.Collection集合
1.1 集合概述
- 集合:集合是java中提供的一种容器,可以用来存储多个数据。
- 集合和数组既然都是容器,它们有什么区别呢?
- 数组的长度是固定的。集合的长度是可变的。
- 数组中存储的是同一类型的元素,可以存储任意类型数据。集合存储的都是引用数据类型。如果想存储基本类型数据需要存储对应的包装类型。
1.2 集合常用类的继承体系
-
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List 和 java.util.Set 。其中, List 的特点是元素有序、元素可重复。 Set 的特点是元素不可重复。 List 接口的主要实现类有 java.util.ArrayList 和 java.util.LinkedList , Set 接口的主要实现类有
java.util.HashSet 和 java.util.LinkedHashSet 。 -
集合常用类的继承体系原理:
1.3 Collection 常用功能
-
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
-
public boolean add(E e) : 把给定的对象添加到当前集合中 。
-
public void clear() :清空集合中所有的元素。
-
public boolean remove(E e) : 把给定的对象在当前集合中删除。
-
public boolean contains(Object obj) : 判断当前集合中是否包含给定的对象。
-
public boolean isEmpty() : 判断当前集合是否为空。
-
public int size() : 返回集合中元素的个数。
-
public Object[] toArray() : 把集合中的元素,存储到数组中
//创建集合对象,多态 //Collection<String> coll = new ArrayList<>();//有序的,可重复的 Collection<String> coll = new HashSet<>();//无重复的,无序的 System.out.println(coll);//[] //public boolean add(E e): 把给定的对象添加到当前集合中 。 coll.add("张三"); coll.add("李四"); coll.add("王五"); coll.add("张三"); coll.add("赵六"); coll.add("田七"); //集合重写了toString方法 [张三, 李四, 王五, 张三, 赵六, 田七] System.out.println(coll); /* public boolean remove(E e): 把给定的对象在当前集合中删除。 1.如果集合中有对应的元素,会移除成功,返回true 2.如果集合中没有有对应的元素,对集合没有影响,返回false 3.如果被移除的元素在集合中有多个,只会移除第一个 */ boolean b1 = coll.remove("张三"); System.out.println("b1:"+b1); //b1:true boolean b2 = coll.remove("赵四"); System.out.println("b2:"+b2); //b2:false System.out.println(coll);//[李四, 王五, 张三, 赵六, 田七] /* public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。 1.包含,返回true 2.不包含,返回false */ boolean b3 = coll.contains("王五"); System.out.println("b3:"+b3);//b3:true boolean b4 = coll.contains("老王"); System.out.println("b4:"+b4);//b4:false /* public int size(): 返回集合中元素的个数。 */ System.out.println(coll.size());//5 /* public Object[] toArray(): 把集合中的元素,存储到数组中 */ Object[] arr = coll.toArray(); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } /* public boolean isEmpty(): 判断当前集合是否为空。 1.集合为空,返回true 2.集合不为空,返回false */ System.out.println(coll.isEmpty());//false /* public void clear() :清空集合中所有的元素。 集合还存在,还能继续使用 */ coll.clear(); System.out.println(coll);//[] System.out.println(coll.isEmpty());//true
-
二.Iterator迭代器
2.1 Iterator接口
-
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口
java.util.Iterator 。 -
由来:集合有很多种,每种集合存储数据的原理不同,取出元素的方式也不同,所以我们可以使用迭代器,来取出集合中的元素,是一种通用的取出集合中元素的方式。
-
迭代器是一个接口:java.util.Iterator:迭代器接口 对 collection 进行迭代的迭代器。
-
迭代器的使用:在Collection集合中有一个方法叫iterator()
-
它返回Iterator接口的实现类对象(了解:迭代器的实现类是每个集合的内部类)
-
Iterator iterator() 返回在此 collection 的元素上进行迭代的迭代器。
-
我们无需关注Iterator接口的实现类是谁,我们只需要知道Iterator接口中的方法如何使用会使用
-
Iterator接口来接收这个实现类对象即可,这种编程方式(只关注接口)叫面向接口编程。
-
迭代器的使用步骤:(重点):
- 创建集合对象,往集合中添加元素
- 使用集合中的方法iterator获取迭代器的对象
- 使用迭代器Iterator中的方法hasNext和next依次取出集合中的元素
-
-
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
-
public Iterator iterator() : 获取集合对应的迭代器,用来遍历集合中的元素的。
-
迭代的原理:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
-
Iterator接口的常用方法如下:
-
public E next() :返回迭代的下一个元素。
-
public boolean hasNext() :如果仍有元素可以迭代,则返回 true。
-
接下来我们通过案例学习如何使用Iterator迭代集合中元素:
//1.创建集合对象,往集合中添加元素 Collection<String> coll = new ArrayList<>(); //Collection<String> coll = new HashSet<>(); coll.add("詹姆斯"); coll.add("姚明"); coll.add("科比"); coll.add("库里"); coll.add("艾弗森"); /* 2.使用集合中的方法iterator获取迭代器的对象 迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型 */ Iterator<String> it = coll.iterator(); /* 3.使用迭代器Iterator中的方法hasNext和next依次取出集合中的元素 1.boolean hasNext():判断集合中还有没有下一个元素,有就返回true,没有就返回false 2.E next():取出集合中的下一个元素 发现迭代器取出元素是一个重复的过程,所以可以使用循环优化 不知道集合中有多少元素,使用while循环 while循环结束条件,hasNext方法返回false */ while (it.hasNext()){ String s = it.next(); System.out.println(s); } System.out.println("---------------------------"); for(Iterator<String> it2 = coll.iterator();it2.hasNext();){ String s = it2.next(); System.out.println(s); }
-
-
注意事项:
-
在进行集合元素获取时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。
-
在进行集合元素获取时,如果添加或移除集合中的元素 , 将无法继续迭代 , 将会抛出
ConcurrentModificationException并发修改异常.–>并发(遍历和修改同时进行)/*并发修改异常:并发修改异常:在使用迭代器遍历集合的过程中,对集合的长度进行了修改(增加元素,删除元素)并发:遍历和修改同时进行-->ConcurrentModificationException 解决方案: 1.迭代就是迭代,不要对集合进行修改 2.想要在遍历的过程中修改,可以使用Iterator接口的子接口ListIterator public interface ListIterator<E>extends Iterator<E>{ 里边定义了往集合中添加元素和删除元素的方法 void add(E e):将指定的元素插入列表 void remove():从列表中移除由next或previous返回最后一个元素。 可以使用迭代器的add/remove方法对集合进行修改,就相当于迭代器和集合已经商量好了,可以增删元素 } 注意: ListIterator接口只能遍历List接口下边的集合 */ ArrayList<String> list = new ArrayList<>(); list.add("詹姆斯"); list.add("姚明"); list.add("科比"); list.add("库里"); list.add("艾弗森"); //使用集合中的方法iterator获取迭代器对象 Iterator<String> it = list.iterator(); //使用迭代中的方法hasNext和next依次取出集合中的元素 while (it.hasNext()){ String s = it.next(); System.out.println(s); /* 1.增加一个判断,如果存在科比 2.增加一个奥尼尔 */ if(s.equals("科比")){ list.add("奥尼尔"); } //list.remove(s);默认删除集合中元素第一个位置的詹姆斯 } System.out.println("-------------------------------------"); //使用集合中的方法listIterator获取迭代器对象 ListIterator<String> li = list.listIterator(); //使用迭代中的方法hasNext和next依次取出集合中的元素 while(li.hasNext()){ String s = li.next(); System.out.println(s); /* 1.增加一个判断,如果存在科比 2.增加一个奥尼尔 */ if(s.equals("科比")){ li.add("奥尼尔");//使用的迭代器增加元素的方法 } } System.out.println(list);//[詹姆斯, 姚明, 科比, 奥尼尔, 库里, 艾弗森]
-
并发修改异常原理:
-
-
2.2 迭代器的实现原理
- 在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
2.3 增强for
-
增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。格式:
for(元素的数据类型 变量 : Collection集合or数组){ //写操作代码 }
-
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
-
新for循环必须有被遍历的目标,目标只能是Collection或者是数组;
-
新式for(迭代器)仅仅作为遍历操作出现,不能对集合进行增删元素操作,否则抛出
ConcurrentModificationException并发修改异常/* 使用增强的for循环(for each)遍历数组 */ int[] arr={1,2,3}; for(int a:arr){ System.out.println(a); } /* 增强的for循环和普通for循环的区别 1:普通for在遍历的过程中可以对集合中的数组/集合中元素进行修改(索引) 2:增强的for没有索引,也不能修改数组/集合中的元素 */ int[] arr1 = {1,2,3}; //使用普通的for循环遍历数组 for (int i = 0; i < arr1.length; i++) { //把数组中的元素扩大二倍 arr1[i] *= 2; System.out.println(arr1[i]); } System.out.println("arr1[0]: "+arr1[0]);//2 System.out.println("-------------------------"); int[] arr2 = {1,2,3}; //使用增强for循环遍历数组 for (int i : arr2) { //把数组中的元素扩大二倍 i *= 2; System.out.println(i); } System.out.println("arr2[0]: "+arr2[0]);//1 /* 增强for循环遍历的同时,可以使用元素特有的方法 */ String[] arr = {"aaa","2134214234","safdsaljflsakf"}; for(String s: arr){ System.out.println(s+"-->"+s.length()); } /* 使用增强for循环遍历集合 */ ArrayList<Integer> list = new ArrayList<>(); list.add(1);list.add(2); list.add(null);//能接受空,但int类型不能接受,会报空指针异常 list.add(3);list.add(4); /*for(int i : list){//自动拆箱 System.out.println(i); }*/ for (Integer in : list) { System.out.println(in); }
-
for循环和普通for循环的区别原理
三.泛型
3.1 泛型概述
-
泛型是一种未知的数据类型.当我们不知道什么什么类型的时候,就可以使用泛型
-
泛型也可以看成一个变量,用来接受数据类型(E:element 元素,T: type类型)
-
集合在定义的时候,并不知道要存储什么类型的元素,就得使用泛型
3.2 使用泛型的好处
-
创建集合对象,不使用泛型
- 好处:集合的类型默认为Object类型,可以存储任意的对象
- 弊端:不安全,不能使用元素特有的方法
ArrayList list = new ArrayList(); list.add("abc"); list.add(1); //使用迭代器遍历集合 Iterator it = list.iterator(); while(it.hasNext()){ //取出集合中元素,Object类型 Object obj = it.next(); System.out.println(obj); /* 想使用字符串特有的方法length 不能使用,需要向下转型 Object obj = "abc";多态 */ String s = (String)obj; System.out.println(s.length()); }
-
创建集合对象,使用泛型
- 好处:
- 想使用元素特有的方法,避免了类型转换的麻烦
- 把运行时异常,提升到编译时期异常
- 缺点:
- 只能存储对应泛型的数据
ArrayList<String> list = new ArrayList<>(); list.add("abc"); //list.add(1); //使用增强for遍历list集合 for(String s : list){ System.out.println(s+"-->"+s.length()); }
- 好处:
3.3 泛型的定义与使用
- 我们在集合中会大量使用泛型,泛型,用来灵活地将数据类型应用到不同的类,方法,接口中。将数据类型作为参数进行传递。
3.3.1 定义和使用含有泛型的类
-
定义格式:
修饰符 class 类名<代表泛型的变量>{ }
-
定义一个含有泛型的类,模拟ArrayList集合:
-
什么时候使用泛型,当我们不知道使用什么类型时,使用泛型(泛型:代表未知的数据类型)
-
什么时候确定泛型的数据类型:创建对象的时候
//泛型的定义格式:<名称>--》泛型实例对象的类 public class GenericClass<C> { private C a; public C getA() {return a;} public void setA(C a) {this.a = a;} } //测试类 //创建GenericClass对象,泛型使用Integer GenericClass<Integer> gc1 = new GenericClass<>(); gc1.setA(1); Integer a = gc1.getA(); System.out.println(a); //创建GenericClass对象,泛型使用Boolean GenericClass<Boolean> gc2 = new GenericClass<>(); gc2.setA(true); Boolean b2 = gc2.getA(); System.out.println(b2);
-
3.3.2 含有泛型的方法
-
定义含义泛型的方法:
-
泛型定义在修饰符和返回值类型之间
-
格式:
修饰符<泛型> 返回值类型 方法名(参数列表(使用泛型)){方法体;}
-
什么时候确定泛型的数据类型:调用方法的时候确定泛型的数据类型,传递什么类型的参数,泛型就是什么类型
//定义一个含有泛型的方法 public <M> void show(M m){System.out.println(m);} //定义一个含有泛型的静态方法 public static <S> void method(S s){System.out.println(s);} //创建GenericMethod对象 GenericMethod gm = new GenericMethod(); //调用含有泛型的show方法 gm.show(1);//泛型就是Integer类型 gm.show("abc");//泛型就是String类型 gm.show(true);//泛型就是Boolean类型 gm.show(8.8);//泛型就是Double类型 gm.show('a');//泛型就是Character类型 //通过类名直接调用静态方法 GenericMethod.method(1); GenericMethod.method("abc");
-
3.3.4 含有泛型的接口
-
定义一个含义泛型的接口:
-
格式:
public interface 接口名<I>{ //定义一个抽象方法 public abstract void show(I i); }
-
-
定义一个含义泛型的接口实现类
-
第一种使用方式:(类实现接口的同时,指定接口泛型的数据类型,这样数据类型就确定下来了)
-
格式:
public class GenericInterfaceImpl1 implements GenericInterface<String>{ @Override public void show(String s) { System.out.println(s); } }
-
第二种使用方式:(类实现含义泛型的接口,接口使用什么类型,类就使用什么类型,类跟着接口走,就相当于定义了一个含义泛型的类)
-
格式:
public class GenericInterfaceImpl2<I> implements GenericInterface<I>{ @Override public void show(I i) { System.out.println(i); } }
//实现类 //创建含有泛型接口GenericInterface的实现类GenericInterfaceImpl1对象 GenericInterfaceImpl1 gi1 = new GenericInterfaceImpl1(); gi1.show("aaa"); //创建含有泛型接口GenericInterface的实现类GenericInterfaceImpl2对象 //在创建对象的同时,指定泛型的类型 GenericInterfaceImpl2<Double> gi2 = new GenericInterfaceImpl2(); gi2.show(8.8); GenericInterfaceImpl2<Integer> gi3 = new GenericInterfaceImpl2(); gi3.show(1);
-
3.4 泛型通配符
- 当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
3.4.1 通配符基本使用
-
泛型的通配符:不知道用什么类型来接受的时候,此时可以使用(?)
-
?:表示未知通配符,代表任意的数据类型。
-
使用方式:
-
不能创建对象的时候作为泛型使用
//不能创建对象的时候作为泛型使用 //ArrayList<?> list = new ArrayList<>(); list.add(1);//报错,不能创建
-
作为方法的参数泛型使用
ArrayList<Integer> list01 = new ArrayList<>(); list01.add(1); list01.add(2); ArrayList<String> list02 = new ArrayList<>(); list02.add("a"); list02.add("b"); printArrayList(list01); printArrayList(list02); /* 定义一个方法,对ArrayList集合进行遍历 要求:可以遍历任意数据类型的ArrayList集合 */ public static void printArrayList(ArrayList<?> list){ //使用迭代器遍历集合,迭代器的泛型和集合一样 Iterator<?> it = list.iterator(); while(it.hasNext()){ //取出元素是Object类型 Object obj = it.next(); System.out.println(obj); } }
-
-
-
此时只能接受数据,不能往集合中存储数据。
3.42 通配符高级应用–受限泛型
-
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在java的泛型中可以指定一个泛型的上限和下限。
-
泛型的上限:
-
格式:类型名称<? extends 类> 对象名称
? extends E 代表使用的泛型只要是E类型的子类/本身即可
-
意思:只能接受该类型及其子类型
-
-
泛型的下限:
-
格式:类型名称<? super 类> 对象名称
? super E 代表使用的泛型只要是E类型的父类/本身即可
-
意思:只能接收该类型及其父类型
-
-
案例:
Collection<Integer> list1 = new ArrayList<Integer>(); Collection<String> list2 = new ArrayList<String>(); Collection<Number> list3 = new ArrayList<Number>(); Collection<Object> list4 = new ArrayList<Object>(); /* 类与类之间的继承关系: Ingteger extends Number extends Object String extends Object */ getElement1(list1); //getElement1(list2);//报错 String和Number没有关系 getElement1(list3); //getElement1(list4);//报错 传递的是Number的父类 //getElement2(list1);//报错 传递的是Number的子类 //getElement2(list2);//报错 String和Number没有关系 getElement2(list3); getElement2(list4); } // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类 public static void getElement1(Collection<? extends Number> coll){} // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类 public static void getElement2(Collection<? super Number> coll){}
-