一、前言
<font face=黑体>因为明年三四月份想换工作,也为了对 Java 语言有更深的理解,所以想重新学一遍 Java,把以前没有注意到的细节再巩固一下,因为现在时间还是有的,所以我决定重新的过一遍 Java 的知识点(至少比粗暴的刷 Java 面试题好)。我这里准备的学习资料主要有十大章,我们来看一下每章的目录名称(用的 Java 版本是目前用的最多的版本 Java 8):
- Java 基础语法;
- 面向对象和封装;
- 常用 API 第一部分;
- 继承与多态;
- 常用 API 第二部分;
- 集合;
- 异常与多线程;
- File 类与 IO 流;
- 网络编程;
- JDK 8 新特性。
<font face=黑体>但是我不打算从第一章开始看,我决定从第 6 章 集合 开始,等 6 到 10 章学完,再学 1 到 5 章,我们来看一下集合这一章的目录:
- Collection 集合;
- 泛型;
- 综合案例 1;
- List 集合;
- Set 集合;
- Collections 工具类;
- Map 集合;
- 综合案例 2;
<font face=黑体>集合这一章的内容确实有点多,所以我们分三部分来讲解,让我们开始。
二、Collection 集合
2.1、集合概述
<font face=黑体>集合是 Java 中提供的一种容器,可以用来存储多个数据。集合和数组都是容器,它们的区别如下:
- <font face=黑体>数组的长度是固定的,集合的长度是可变的。
- <font face=黑体>数组中存储的是统一类型的元素,可以存储基本数据类型,也可以存储对象。集合存储的都是对象,不可以存储基本数据类型,而且对象的类型可以不一致。
2.2、集合框架
<font face=黑体>集合的框架如下所示,需要注意的点有:
- <font face=黑体>TreeSet 与 HashSet 是无序的,即存和取的元素的顺序有可能不一致,但是 LinkedHashSet 是有序的。
- <font face=黑体>顶层不是接口就是抽象类,无法创建对象使用,需要底层的子类创建对象使用。
2.3、Collection 集合常用功能
<font face=黑体>Collection 是所有单列集合的父接口,因此在 Collection 中定义了单列集合(List 和 Set)通用的一些方法,这些方法可用于操作所有的单列集合,方法如下:
- <font face=黑体>public boolean add(E e):把给定的对象添加到当前集合中。
- <font face=黑体>public void clear():清空集合中的所有元素。
- <font face=黑体>public boolean remove(E e):把给定的对象在当前集合中删除。
- <font face=黑体>public boolean contains(E e):判断当前集合中是否包含给定的对象。
- <font face=黑体>public boolean isEmpty():判断当前集合是否为空。
- <font face=黑体>public int size():返回集合中元素个数。
- <font face=黑体>public Object[] toArray():把集合转化成数组。
<font face=黑体>Collection 常用功能代码演示如下所示,输出结果大家可以自己复制到 Intellij IDEA 中查看。
public class CollectionDemo01 {
public static void main(String[] args) {
// 创建集合对象
Collection<String> coll = new ArrayList<>();
System.out.println(coll); // 重写了toSting() 方法
/**
* public boolean add(E e):把给定的对象添加到当前集合中。
* 返回值是一个 boolean 值,一般都返回 true,可以不用接收
*/
boolean b1 = coll.add("张三");
System.out.println("b1:" + b1); // b1:true
System.out.println(coll); // [张三]
coll.add("李四");
coll.add("王五");
coll.add("赵六");
coll.add("田七");
System.out.println(coll); // [张三, 李四, 王五, 赵六, 田七]
System.out.println("---------------------分割线-----------------------");
System.out.println();
/**
* public boolean remove(E e):把给定的对象在当前集合中删除。
* 返回值是一个 boolean 值,集合中存在元素,删除元素,返回 true,集合中不存在的话,删除失败,返回 false
*/
boolean b2 = coll.remove("赵六");
System.out.println("b2:" + b2); // b2:true
boolean b3 = coll.remove("赵四");
System.out.println("b3:" + b3); // b3:false
System.out.println(coll); // [张三, 李四, 王五, 田七]
System.out.println("---------------------分割线-----------------------");
System.out.println();
/**
* public boolean contains(E e):判断当前集合中是否包含给定的对象。
* 包含返回 true,不包含返回 false
*/
boolean b4 = coll.contains("李四");
System.out.println("b4:" + b4); // b4:true
boolean b5 = coll.contains("赵四");
System.out.println("b5:" + b5); // b5:false
System.out.println("---------------------分割线-----------------------");
System.out.println();
/**
* public boolean isEmpty():判断当前集合是否为空。
*/
boolean b6 = coll.isEmpty();
System.out.println("b6:" + b6); // b6:false
System.out.println("---------------------分割线-----------------------");
System.out.println();
/**
* public int size():返回集合中元素个数。
*/
int size = coll.size();
System.out.println("size:" + size);
System.out.println("---------------------分割线-----------------------");
System.out.println();
/**
* public Object[] toArray():把集合转化成数组。
*/
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println("---------------------分割线-----------------------");
System.out.println();
/**
* public void clear():清空集合中的所有元素。
* 但是不删除集合,集合还存在
*/
coll.clear();
System.out.println(coll); // []
System.out.println(coll.isEmpty()); // true
}
}
三、Iterator 迭代器
3.1、Iterator 接口概述
<font face=黑体>在程序开发中,经常需要遍历集合中的元素,针对这种需求,JDK 专门提供了一个接口 Iterator。Iterator 接口也是 Java 集合中的一员,主要用于迭代访问(即遍历)Collection 中的元素,因为 Iterator 对象也被称为迭代器,是一种通用的取出集合中元素的方式。
3.2、Iterator 接口常用方法
<font face=黑体>常用方法有一下两个:
- <font face=黑体>public boolean hasNext():如果仍有元素,则返回 true。
- <font face=黑体>public E next():返回迭代的下一个元素。
3.3、迭代器的使用步骤
<font face=黑体>由于 Iterator 迭代器是一个接口,我们无法直接使用,需要使用 Iterator 接口的实现类对象,通过 Collection 接口中的 iterator() 方法返回迭代器的实现类对象。使用步骤如下:
- <font face=黑体>使用集合中的 iterator() 方法获得迭代器的实现类对象,使用 Iterator 接口接收;
- <font face=黑体>使用 Iterator 中的方法 hasNext() 判断还有没有下一个元素;
- <font face=黑体>使用 Iterator 中的方法 next() 取出集合中的元素。
<font face=黑体>注意:Iterator 接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型。
3.4、增强 for 循环
<font face=黑体>增强 for 循环(也称 forEach 循环)是 JDK 1.5以后出来的一个高级 for 循环,专门用来遍历数组和集合,它的内部原理其实是个 Iterator 迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
<font face=黑体>格式:for(集合/数组的数据类型 变量名 : 集合名/数组名)
<font face=黑体>for(int i : arr)
<font face=黑体>Iterator 和增强 for 循环遍历集合代码演示如下所示,输出结果大家可以自己复制到 Intellij IDEA 中查看。
public class IteratorDemo02 {
public static void main(String[] args) {
// 创建集合对象
Collection<String> coll = new ArrayList<>();
coll.add("乔丹");
coll.add("詹姆斯");
coll.add("奥尼尔");
coll.add("艾佛森");
coll.add("科比");
/**
* 1、使用集合中的 iterator() 方法获得迭代器的实现类对象,使用 Iterator 接口接收;
* 注意:Iterator<E> 接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型
* 2、使用 Iterator 中的方法 hasNext() 判断还有没有下一个元素
* 3、使用 Iterator 中的方法 next() 取出集合中的元素。
*/
Iterator<String> it = coll.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println("---------------------分割线-----------------------");
System.out.println();
// 利用 for 循环改写 while 循环
for ( Iterator<String> it1 = coll.iterator();it1.hasNext();) {
System.out.println(it1.next());
}
System.out.println("---------------------分割线-----------------------");
System.out.println();
// 可以用增强 for 循环遍历(不能用普通 for 循环遍历)
for (String s : coll) {
System.out.println(s);
}
}
}
3.5、迭代器的工作原理
- <font face=黑体>Iterator<String> it = coll.iterator():获取迭代器的实现类对象,并把引用(可以理解成指针)指向集合中的 -1 索引;
- <font face=黑体>it.hasNext():判断集合中还有没有下一个元素;
- <font face=黑体>it.next():取出下一个元素,并把指针(这里用指针来说明好理解一点)向后移动一位。
四、泛型
4.1、泛型概述
<font face=黑体>泛型是一种未知的数据类型,当我们不知道用什么数据类型的时候,可以使用泛型,泛型也可以看成是一个变量,用来接收数据类型。ArrayList 集合在定义的时候(源码中就是利用泛型的),不知道集合中都会存储什么类型的数据,所以使用泛型。
4.2、使用泛型的优点与弊端
<font face=黑体>集合不使用泛型,默认的类型就是 Object 类型,可以存储任意类型的数据,但是同时也使得集合变得不安全,容易引发异常,所有使用泛型的优点有:
- <font face=黑体>避免了类型转化的麻烦;
- <font face=黑体>把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候就会报错)。
<font face=黑体>使用泛型创建集合和不使用泛型创建集合代码演示如下所示,输出结果大家可以自己复制到 Intellij IDEA 中查看。
public class GenericDemo01 {
public static void main(String[] args) {
// 不使用泛型
//show01();
// 使用泛型
show02();
}
/**
* 创建集合对象,不使用泛型
* 好处:
* 集合不使用泛型,默认的类型就是 Object 类型,可以存储任意类型的数据
* 弊端:
* 不安全,容易引发异常
*/
private static void show01() {
ArrayList list = new ArrayList();
list.add("abc");
list.add(1);
// 使用迭代器遍历
Iterator it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
System.out.println(obj);
String s = (String) obj;
// 运行后,会有异常
System.out.println(s.length()); // java.lang.ClassCastException: Integer cannot be cast to String
}
}
/**
* 创建集合对象,使用泛型
* 好处:
* 1、避免了类型转化的麻烦
* 2、把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候就会报错)
* 弊端:
* 泛型是什么类型,只能存储什么类型的数据
*/
private static void show02() {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
// 使用迭代器遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
System.out.println(s.length());
}
}
}
4.3、定义含有泛型的类
<font face=黑体>含有泛型的类定义如下代码所示:
public class GenericDemo02<E> {
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
public static void main(String[] args) {
GenericDemo02<String> g1 = new GenericDemo02();
g1.setName("testGeneric");
System.out.println(g1.getName());
GenericDemo02<Integer> g2 = new GenericDemo02();
g2.setName(123);
System.out.println(g2.getName());
}
}
4.4、定义含有泛型的方法
<font face=黑体>定义含有泛型的方法,泛型需要定义在方法的修饰符和返回值类型之间,定义格式如下所示:
修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型))
public <E> void method01(E e) {
System.out.println(e);
}
// 静态方法
public static <E> void method2(E e) {
System.out.println(e);
}
4.5、定义并使用含有泛型的接口
<font face=黑体>含有泛型的接口如下所示:
public interface GenericInterface04<I> {
public abstract void method(I i);
}
<font face=黑体>含有泛型的接口的使用方式有两种:
<font face=黑体>在定义接口的实现类的时候指定接口的泛型。
public class GenericInterface04Impl1 implements GenericInterface04<String> { @Override public void method(String s) { System.out.println(s); } }
<font face=黑体>接口使用什么泛型,实现类就使用什么泛型,类跟着接口走。就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型;
public class GenericInterface04Impl2<I> implements GenericInterface04<I> { @Override public void method(I i) { System.out.println(i); } }
4.6、泛型通配符
<font face=黑体>当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符 <?> 表示,但是使用通配符只能作为方法的参数传递,不能定义对象。泛型通配符的使用如下所示:
public class GenericDemo05 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
ArrayList<String> list2 = new ArrayList<>();
list2.add("a");
list2.add("b");
printArray(list1);
printArray(list2);
}
public static void printArray(ArrayList<?> list) {
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
4.7、受限泛型
<font face=黑体>泛型的上限限定:
<font face=黑体>格式:类型名称 <? extends E> 对象名称
<font face=黑体>意义:只能接受 E 类型及其子类。
<font face=黑体>泛型的下限限定:
<font face=黑体>格式:类型名称 <? super E> 对象名称
<font face=黑体>意义:只能接受 E 类型及其父类。
<font face=黑体>受限泛型代码演示如下所示,大家可以自己复制到 Intellij IDEA 中查看。
public class GenericDemo06 {
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<>();
Collection<String> list2 = new ArrayList<>();
Collection<Number> list3 = new ArrayList<>();
Collection<Object> list4 = new ArrayList<>();
/**
* Integer extends Number extends Object
* String extends Object
*/
getElement1(list1);
getElement1(list2); // 报错
getElement1(list3);
getElement1(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) {
}
}
五、集合综合案例——斗地主
<font face=黑体>综合案例步骤:
- <font face=黑体>准备牌:
54 张牌存储到一个集合中;
特殊牌:大王,小王;
其他 52 张牌:
定义一个数组,存储 4 种花色 $\color{red}{♥ ♦}$ $\color{black}{♠ ♣}$;
定义一个数组,存储 13 个序号[2、A、K...3];
循环嵌套遍历上面两个数组,组装 52 张牌[$\color{red}{♥2}$,$\color{red}{♦A}$,$\color{black}{♥K}$,$\color{black}{♣Q}$,...];
- <font face=黑体>洗牌
利用集合工具类 Collections.shuffle() 来随机打乱集合中元素的位置;
- <font face=黑体>发牌
发牌要求如下:
一人 17 张牌,剩余 3 张作为底牌,一人一张轮流发牌(集合的索引 % 3);
定义四个集合,存储 3 个玩家的牌和底牌;
- <font face=黑体>看牌
直接打印集合,遍历存储玩家和底牌的集合;
<font face=黑体>具体实现代码如下所示:
public class DouDiZhu {
public static void main(String[] args) {
// 1、准备牌
// 定义一个存储 54 张牌的集合;
ArrayList<String> poker = new ArrayList<>();
// 定义两个集合,一个存储牌的花色,一个存储牌的序号
String[] colors = {"♠", "♥", "♣", "♦"};
String[] numbers = {"2", "A", "k", "q", "J", "10", "9", "8", "7", "6", "5", "4", "3"};
// 先把大王和小王存储到poker集合中
poker.add("大王");
poker.add("小王");
// 循环嵌套两个数组,组装52张牌
for (String number : numbers) {
for (String color : colors) {
// 把组装好的牌存到poker集合中
poker.add(color + number);
}
}
// 2、洗牌
// 利用集合工具类 Collections.shuffle() 来随机打乱集合中元素的位置;
Collections.shuffle(poker);
// 3、发牌
// 定义四个集合,存储 3 个玩家的牌和底牌;
ArrayList<String> player01 = new ArrayList<>();
ArrayList<String> player02 = new ArrayList<>();
ArrayList<String> player03 = new ArrayList<>();
ArrayList<String> dipai = new ArrayList<>();
// 4、遍历poker集合,获取每一种牌
// 使用poker集合的索引 % 3 给三个玩家轮流发牌,最后剩余三张牌给底牌
for (int i = 0; i < poker.size(); i++) {
String p = poker.get(i);
if (i >= 51) {
dipai.add(p);
} else if (i % 3 == 0) {
player01.add(p);
} else if (i % 3 == 1) {
player02.add(p);
} else {
player03.add(p);
}
}
// 方便起见直接打印
System.out.println("玩家1的牌:" + player01);
System.out.println("玩家2的牌:" + player02);
System.out.println("玩家3的牌:" + player03);
System.out.println("底牌:" + dipai);
}
}
<font face=黑体>打印结果如下所示:
六、小结
<font face=黑体>集合第一部分中我们讲了 Collection集合接口、Iterator 迭代器、泛型和斗地主综合案例,下一节我们讲数据结构、List 集合和 Set 集合。
七、源码
<font face=黑体>文章中用到的所有源码已上传至 github,有需要的可以去下载。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。