文章目录
1、集合概述
1.1、什么是集合
集合:集合是java中提供的一种容器,可以用来存储多个数据。
问题:集合和数组既然都是容器,它们有啥区别呢?
- 数组的长度是固定的。集合的长度是可变的。
- 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
1.2、集合框架
JAVASE提供了满足各种需求的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活应用。
集合本身是一个工具,它存放在java.util
包中。
集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection
和双列集合java.util.Map
。
2、Collection集合
2.1、基本介绍
- Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素。它有两个重要的子接口,分别是
java.util.List
和java.util.Set
。Collection接口中定义的是所有单列集合中共性的方法,即所有单列集合都可以使用的方法。该接口没有带索引的方法。 - List:该集合特点是元素有序(存储和取出的顺序相同)、元素可重复、有索引(可以使用普通的for循环遍历)。
List
接口的主要实现类有java.util.Vector
、java.util.ArrayList
和java.util.LinkedList
。 - Set:该集合特点是元素无序(相对而言,TreeSet和HashSet无序,LinkedHashSet有序)、元素不可重复、没有索引(不可以使用普通的for循环遍历)。
Set
接口的主要实现类有java.util.HashSet
和java.util.TreeSet
2.2、常用方法
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
方法 | 描述 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素,不删除集合 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(E e) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
方法演示:
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo {
public static void main(String[] args) {
// 使用多态创建集合对象
Collection<String> coll = new ArrayList<>();
System.out.println(coll); //结果为[],重写了toString方法
/* add:把给定的对象添加到当前集合中,返回值为boolean值
*/
boolean b1 = coll.add("Atlantis");
coll.add("Olivia");
coll.add("长安");
System.out.println("b1:" + b1); //结果为b1:true
System.out.println(coll); //结果为[Atlantis, Olivia, 长安]
/* size:返回集合中元素的个数。
*/
int size = coll.size();
System.out.println("size:" + size); //结果为size:3
/* toArray:把集合中的元素,存储到数组中。
*/
Object[] array = coll.toArray();
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " "); //结果为Atlantis Olivia 长安
}
/* remove:把给定的对象在当前集合中删除,返回值为boolean值
集合中存在元素,删除元素,返回true
集合中不存在元素,删除失败,返回false
*/
boolean b2 = coll.remove("长安");
System.out.println("b2:" + b2); //结果为b2:true
boolean b3 = coll.remove("Andersen");
System.out.println("b3:" + b3); //结果为b3:false
System.out.println(coll); //结果为[Atlantis, Olivia]
/* contains:判断当前集合中是否包含给定的对象。
包含返回true,否则为false
*/
boolean b4 = coll.contains("Olivia");
System.out.println("b4:" + b4); //结果为b4:true
boolean b5 = coll.contains("Andersen");
System.out.println("b5:" + b5); //结果为b5:false
/* isEmpty:判断当前集合是否为空。
集合为空返回true,集合不为空返回false
*/
boolean b6 = coll.isEmpty();
System.out.println("b6:" + b6); //结果为b6:false
/* clear:清空集合中所有的元素,不删除集合,无返回值。
*/
coll.clear();
System.out.println(coll); //结果为[]
System.out.println(coll.isEmpty()); //结果为true
}
}
Tips: 有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。
3、Iterator迭代器
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
3.1、Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator
。Iterator
接口也是Java集合中的一员,但它与Collection
、Map
接口有所不同,Collection
接口与Map
接口主要用于存储元素,而Iterator
主要用于迭代访问(即遍历)Collection
中的元素,因此Iterator
对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,获取迭代器的方法:
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。
Iterator接口的常用方法如下:
public E next()
:返回迭代的下一个元素。
public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
3.2、使用方法
因为Iterator
迭代器是一个接口,我们无法直接使用,需要使用Iterator
接口的实现类对象。而Collection接口中有一个方法为iterator()
,这个方法返回的就是迭代器的实现对象。
迭代器的使用步骤:
(1)使用集合中的方法iterator()
获取迭代器的实现类对象,使用Iterator
接口接收(多态)。
(2)使用Iterator
接口中的方法hasNext()
判断是否存在迭代对象。
(3)使用Iterator
接口中的方法next()
获取集合中的下一个元素。
接下来我们学习如何使用Iterator
迭代集合中元素:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
// 使用多态创建集合对象
Collection<String> coll = new ArrayList<>();
// 添加元素到集合
coll.add("Atlantis");
coll.add("Olivia");
coll.add("长安");
// 使用迭代器进行遍历
// coll.iterator():每个集合对象都有自己的迭代器
// Iterator<E>:接口是有泛型的,迭代器的泛型与集合泛型一致。
Iterator<String> iterator = coll.iterator();
while (iterator.hasNext()) { //判断是否存在 迭代对象
String str = iterator.next(); //获取迭代出的元素
System.out.print(str + " "); //结果为Atlantis Olivia 长安
}
}
}
Tips: 在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生
java.util.NoSuchElementException
没有集合元素的错误。
3.3、实现原理
我们在上一小节已经完成了Iterator
遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()
方法获得迭代器对象,然后使用hashNext()
方法判断集合中是否存在下一个元素,如果存在,则调用next()
方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
Iterator
迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator
对象迭代元素的过程:
在调用Iterator
的next()
方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next()
方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next()
方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hashNext()
方法返回false,表示到达了集合的末尾,终止对元素的遍历。
3.4、增强for循环
增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator
迭代器,使用for循环的格式简化了迭代器的书写。增强for循环用于遍历Collection和数组,通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
所有的单列集合都可以使用增强for循环来遍历数据。
增强for循环格式:
for(元素的数据类型 变量 : Collection集合or数组){
//写操作代码
}
使用增强for循环来遍历数组和集合:
/* 遍历数组 */
int[] arr = {3, 5, 6, 87};
// 使用增强for循环数组
for (int a : arr) {//a代表数组中的每个元素
System.out.println(a);
}
/* 遍历集合 */
Collection<String> coll = new ArrayList<>();
coll.add("Atlantis");
coll.add("Olivia");
coll.add("长安");
// 使用增强for循环遍历
for (String str : coll) {//接收变量str代表被遍历到的集合元素
System.out.println(str);
}
Tips: 增强for循环必须有被遍历的目标。目标只能是Collection或者是数组。增强for循环仅仅作为遍历操作出现。
4、泛型
4.1、泛型的概述
在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。
而泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,就可以使用泛型。当然,泛型也可以看做是一个遍历,用来接收数据类型。
Tips: 一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
4.2、泛型有哪些好处?
介绍使用泛型的好处前,我们先来试一下不使用泛型会怎么样。观察下面代码,程序在运行时发生了异常java.lang.ClassCastException
。
public class GenericDemo {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("Atlantis");
coll.add("Olivia");
coll.add(23); // 由于集合没有做任何限定,任何类型都可以给其中存放
Iterator it = coll.iterator();
while(it.hasNext()){
// 需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
String str = (String) it.next();
System.out.println(str.length());
}
}
}
我们把上面的代码使用泛型进行更改,代码如下:
public class GenericDemo {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("Atlantis");
coll.add("Olivia");
// coll.add(23); // 当集合明确类型后,存放类型不一致就会编译报错
// 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
Iterator<String> it = coll.iterator();
while(it.hasNext()){
String str = it.next();
//当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
System.out.println(str.length());
}
}
}
我们可以发现,当使用泛型后,将原本运行时出现的异常ClassCastException
,转移到了编译时的错误,这样更容易我们发现问题,同时也避免了类型强制的麻烦。
Tips: 泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
4.3、泛型的定义和使用
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
4.3.1、含有泛型的类
定义格式:
修饰符 class 类名<代表泛型的变量> { }
例如,API中的ArrayList集合:
class ArrayList<E>{
public boolean add(E e){ }
public E get(int index){ }
....
}
在创建对象的时候确定泛型
例如,ArrayList<String> list = new ArrayList<String>();
。此时,变量E的值就是String类型,那么我们的类型就可以理解为:
class ArrayList<String>{
public boolean add(String e){ }
public String get(int index){ }
...
}
再例如,ArrayList<Integer> list = new ArrayList<Integer>();
。此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:
class ArrayList<Integer> {
public boolean add(Integer e) { }
public Integer get(int index) { }
...
}
举例自定义泛型类
public class MyGenericClass<MVP> {
//没有MVP类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
private MVP mvp;
public void setMVP(MVP mvp) {
this.mvp = mvp;
}
public MVP getMVP() {
return mvp;
}
}
/* 使用自定义的泛型类 */
public class GenericClassDemo {
public static void main(String[] args) {
// 创建一个泛型为String的类
MyGenericClass<String> my = new MyGenericClass<String>();
// 调用setMVP
my.setMVP("Atlantis");
// 调用getMVP
String mvp = my.getMVP();
System.out.println(mvp);
//创建一个泛型为Integer的类
MyGenericClass<Integer> my2 = new MyGenericClass<Integer>();
my2.setMVP(123);
Integer mvp2 = my2.getMVP();
}
}
4.3.2、含有泛型的方法
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
public class MyGenericMethod {
public <MVP> void show(MVP mvp) {
System.out.println(mvp.getClass());
}
public <MVP> MVP show2(MVP mvp) {
return mvp;
}
}
使用格式:** 调用方法时,确定泛型的类型**
public class GenericMethodDemo {
public static void main(String[] args) {
// 创建对象
MyGenericMethod mm = new MyGenericMethod();
// 演示看方法提示
mm.show("aaa");
mm.show(123);
mm.show(12.45);
}
}
4.3.3、含有泛型的接口
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
public interface MyGenericInterface<E>{
public abstract void add(E e);
public abstract E getE();
}
使用格式:
1、定义类时确定泛型的类型。此时,泛型E的值就是String类型。
public class MyImp1 implements MyGenericInterface<String> {
@Override
public void add(String e) {
// 省略...
}
@Override
public String getE() {
return null;
}
}
2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型。
public class MyImp2<E> implements MyGenericInterface<E> {
@Override
public void add(E e) {
// 省略...
}
@Override
public E getE() {
return null;
}
}
/* 使用 */
public class GenericInterface {
public static void main(String[] args) {
MyImp2<String> my = new MyImp2<String>();
my.add("aa");
}
}
4.4、泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>
表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
4.4.1、基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?
,?
表示未知通配符。此时只能接受数据,不能往该集合中存储数据。
举个栗子大家理解使用即可:
public static void main(String[] args) {
Collection<Intger> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
Tips:泛型不存在继承关系
Collection<Object> list = new ArrayList<String>();
这种是错误的。
4.4.2、受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
格式: 类型名称 <? extends 类 > 对象名称
意义: 只能接收该类型及其子类
泛型的下限:
格式: 类型名称 <? super 类 > 对象名称
意义: 只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类。
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
5、Collections
5.1、常用功能
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:
方法 | 描述 |
---|---|
public static <T> boolean addAll(Collection<T> c,T... elements) | 往集合中添加一些元素 |
public static void shuffle(List<?> list) | 打乱顺序 :打乱集合顺序 |
public static <T> void sort(List<T> list) | 将集合中元素按照默认规则排序 |
public static <T> void sort(List<T> list,Comparator<? super T>) | 将集合中元素按照指定规则排序 |
代码演示:
public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
//原来写法
//list.add(5);
//list.add(20);
//list.add(7);
//list.add(16);
//采用工具类 完成 往集合中添加元素
Collections.addAll(list, 5, 20, 7,16);
System.out.println(list);
//排序方法
Collections.sort(list);
System.out.println(list);
}
}
结果:
[5, 20, 7, 16]
[5, 7, 16, 20]
代码演示之后 ,发现我们的集合按照顺序进行了排列,可是这样的顺序是采用默认的顺序,如果想要指定顺序那该怎么办呢?public static <T> void sort(List<T> list,Comparator<? super T>);
方法就可以将集合中元素按照指定规则排序。
接下来讲解一下指定规则的排列。
5.2、Comparator比较器
public static <T> void sort(List<T> list,Comparator<? super T>);
方法是将集合中元素按照指定规则排序,不过这次存储的字符串类型。
public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
//排序方法
Collections.sort(list);
System.out.println(list);
}
}
结果:
[aba, cba, nba, sba]
我们使用的是默认的规则完成字符串的排序,那么默认规则是怎么定义出来的呢?
说到排序了,简单的说就是两个对象之间比较大小,那么在JAVA中提供了两种比较实现的方式,一种是比较死板的,采用java.lang.Comparable
接口去实现;一种是灵活的,当需要做排序的时候在去选择的java.util.Comparator
接口完成。
那么我们采用的public static <T> void sort(List<T> list)
这个方法完成的排序,实际上要求了被排序的类型。需要实现Comparable接口完成比较的功能,在String类型上如下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
}
String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使用public static <T> void sort(List<T> list,Comparator<? super T>)
方法灵活的完成,这个里面就涉及到了Comparator这个接口,位于java.util
包下。
排序是comparator能实现的功能之一,该接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是:
public int compare(String o1, String o2)
:比较其两个参数的顺序。
两个对象比较的结果有三种:大于,等于,小于。
如果按照升序排序,则o1 小于o2,返回(负数),相等返回0,o1大于o2返回(正数)
如果按照降序排序,则o1 小于o2,返回(正数),相等返回0,o1大于o2返回(负数)
代码演示如下:
public class CollectionsDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
//排序方法 按照第一个单词的降序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) ‐ o1.charAt(0);
}
});
System.out.println(list);
}
}
结果:
[sba, nba, cba, aba]
5.3、Comparable和Comparator的区别
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo()
方法被称为它的自然比较方法。只能在类中实现compareTo()
一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort
(和Arrays.sort
)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort
或Arrays.sort
),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
5.4、练习案例
**需求:**创建一个学生类,存储到ArrayList集合中完成指定的排序操作。
Student学生类代码:
public class Student{
private String name;
private int age;
// 省略无参构造方法
// 省略get、set和toString方法
}
测试类:
public class Demo {
public static void main(String[] args) {
// 创建四个学生对象 存储到集合中
ArrayList<Student> list = new ArrayList<Student>();
list.add(new Student("rose",18));
list.add(new Student("jack",16));
list.add(new Student("abc",16));
list.add(new Student("ace",17));
list.add(new Student("mark",16));
/* 学生按照年龄排序 - 升序 */
// Collections.sort(list); // 要求该list中元素类型必须实现比较器Comparable接口
for (Student student : list) {
System.out.println(student);
}
}
}
此时我们会发现,当调用Collections.sort()
方法的时候程序报错了。原因很简单,如果想要集合中的元素完成排序,那么必须要实现比较器Comparable接口。于是我们修改了Student类的代码,如下:
public class Student implements Comparable<Student>{
....
@Override
public int compareTo(Student o) {
return this.age‐o.age;//升序
}
}
结果:
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}
Student{name='ace', age=17}
Student{name='rose', age=18
扩展一下,如果在使用的时候,想要独立的定义规则去使用可以采用Collections.sort(List list,Comparetor c)
方式,自己定义规则:
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getAge()‐o1.getAge(); // 以学生的年龄降序
}
});
结果:
Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}
如果想要规则更多一些,可以参考下面代码:
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 年龄降序
int result = o2.getAge()‐o1.getAge(); //年龄降序
if(result==0){ //第一个规则判断结束,下一个规则姓名的首字母-升序
result = o1.getName().charAt(0)‐o2.getName().charAt(0);
}
return result;
}
});
结果:
Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='abc', age=16}
Student{name='jack', age=16}
Student{name='mark', age=16}