本帖最后由 849618121 于 2018-11-26 15:01 编辑
Collection接口单列集合顶层
List接口: 元素存取有序, 可重复, 有索引
Set接口: 不可重复, 无索引
学习方法:
学习顶层: 顶层接口/抽象类中共性的方法, 所有子类都可以使用
使用底层: 使用底层子类/实现类创建对象
ArrayList<String> list
list.add("b");
list.add("a");
list.add("c");
list.add("c");
{"b", "a", "c", "c"}
HashSet<String> set
set.add("b");
set.add("a");
set.add("c");
set.add("c");
{"a", "b", "c"}
ArrayList<Integer> list = new ArrayList<>();
Collection<Integer> col = new ArrayList<>(); // 多态, 只在学习时用这种方式创建对象// 为了调用接口中共性的方法, 避免让你调用子类特有方法
java.util.Collection<E>接口:
// 成员方法(子类都会实现)
boolean add(E e): 把给定的对象添加到当前集合中
void clear(): 清空集合中所有的元素
boolean remove(E e): 把给定的对象在当前集合中删除
boolean contains(E e): 判断当前集合中是否包含给定的对象
boolean isEmpty(): 判断当前集合是否为空(没有元素)
int size(): 返回集合中元素的个数
Object[] toArray(): 把集合中的元素,存储到数组中
Iterator<E> iterator(): 获取集合的迭代器对象 (后面讲到)
Iterator接口: 迭代器
测试Collection接口的方式:
使用多态方式创建对象: Collection c = new ArrayList();
编译看左边, 这样只能调用Collection接口中定义的方法, 不会出现子类特有方法
迭代器
集合和迭代器对象的关系:
每个集合都有对应的一个迭代器对象
hasNext() 方法可以判断当前索引是否有元素
next() 方法返回当前元素, 并移动指针到下一个索引
迭代:
类似于遍历, 判断是否有下一个元素, 有则取出下一个, 直到没有
迭代器:
用于遍历集合的对象
java.util.Collection<E>接口:
// 成员方法(子类都会实现)
Iterator<E> iterator(): 获取集合的迭代器对象
java.util.Iterator<E>接口: 迭代器
// 成员方法
boolean hasNext(): 判断是否有下一个元素
E next(): 获取下一个元素
void remove(): 删除next指向的元素
使用迭代器遍历集合的3步:
1. 使用集合对象的 iterator() 获取迭代器对象, 用 Iterator 接口接收.(多态)
2. 使用 Iterator 接口中的 hasNext() 方法, 判断是否有下一个元素
3. 使用 Iterator 接口中的 next() 方法, 获取下一个元素
使用迭代器遍历集合的标准写法:
Iterator<元素类型> iterator = 集合对象.iterator();
while (iterator.hasNext()) {
元素类型 变量名 = iterator.next();
}
List集合
List集合体系的特点:
1. 元素存取有序 (存入和取出元素的顺序一致) 321->321 排序: 从小到大
2. 元素可以重复 1 1 1 1
3. 有索引
List子体系中的实现类都具有上述特点
java.util.List接口:
// 常用特有成员方法 (都是按照索引来操作的)
void add(int index, E element): 将指定的元素, 添加到该集合中的指定位置上
E get(int index): 返回集合中指定位置的元素
E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素
E set(int index, E element): 用指定元素替换集合中指定位置的元素, 返回值的更新前的元素
ArrayList集合的特点
ArrayList底层的数据结构:
数组
ArrayList的特点:
查询快
增删慢
线程不安全, 效率高
ArrayList适用场景:
存储的数据"查询多, 增删少"的场景. 如用一个ArrayList存储中国城市名称
LinkedList集合的特点和特有方法
LinkedList底层的数据结构:
链表
LinkedList的特点:
查询慢
增删快
线程不安全, 效率高
LinkedList适用场景:
存储的数据"查询少, 增删多"的场景. 如用LinkedList实现栈或队列
java.util.LinkedList<E>类: 链表结构, 查询慢, 增删快
// 特有成员方法(主要操作开头和末尾元素)
void addFirst(E e): 将指定元素插入此列表的开头
void addLast(E e): 将指定元素添加到此列表的结尾
E getFirst(): 返回此列表的第一个元素
E getLast(): 返回此列表的最后一个元素
E removeFirst(): 移除并返回此列表的第一个元素
E removeLast(): 移除并返回此列表的最后一个元素
E pop(): (其实就是removeFirst())从此列表所表示的栈中弹出一个元素
void push(E e): (其实就是addFirst())将元素添加到此列表所表示的栈中
Vector集合
Vector底层的数据结构:
数组
Vector的特点:
查询慢
增删快
(同步)线程安全, 效率低
Vector目前几乎没人使用
Set集合
Set集合体系特点:
1. 元素不可重复
2. 没有索引
HashSet特点:
1. 元素不可重复
2. 没有索引
3. 元素存取无序 (存入和取出顺序有可能不一致)
4. 底层采用 哈希表 结构. (查询快)
哈希表 = 数组 + 链表或红黑树
java.util.HashSet类:
// 常用方法
boolean add(E e): 添加元素, 根据元素的 hashCode() 和 equals() 方法判断是否重复. 重复则不添
加并返回false, 不重复则添加并返回true
HashSet原理: 哈希值
哈希值:
一个十进制数值, 一般是通过将该对象的内部地址转换成一个整数来实现的
public native int hashCode();
可以调用系统本地代码(C/C++)计算出一个对象地址的哈希值
hashCode()方法的作用
方法内部的算法用于将对象计算为一个哈希值, 便于根据哈希值比较对象是否"相等"
哈希值主要是为了提高对象存储在 哈希表 中的效率
注意:
1. 如果我们不满意Object中的哈希值计算方法, 可以重写hashCode()方法.
但在Java代码中不能直接重写带有 native 的方法, 重写时应该将 native 去掉
@Override
public int hashCode() {}
2. hashCode() 方法有可能将"不同的对象"计算出"相同的哈希值", 这称为"哈希冲突", 在出现冲突后, 一
般再通过 equals() 方法来继续判断对象是否"相等"
哈希表:
JDK 8以前 : 哈希表 = 数组 + 链表
JDK 8及之后 : 哈希表 = 数组 + 链表或红黑树
数组中存储的每个元素, 是哈希值相同的一组节点的链表或红黑树
HashSet集合保证添加元素不重复的原理:
调用 add(E e) 添加元素时, 先调用 hashCode() 获取哈希值, 和当前HashSet集合中的元素比较
如果哈希值不同, 则认为元素不重复, 添加, 并返回true
如果哈希值相同, 则可能是哈希冲突, 所以继续调用元素的 equals() 方法和所有哈希值相同的元素比较
如果 equals() 比较所有元素都没有相同的, 则认为元素不重复, 添加, 并返回true
如果 equals() 比较出有相同的元素, 则认为元素重复, 不添加, 并返回false
自定义JavaBean对象实现在HashSet中去重:
JavaBean默认继承Object类中的 hashCode() 和 equals() 方法, 都是根据对象地址值判断是否重复的
要根据属性值判断是否重复, 应该在JavaBean类中重写 hashCode() 和 equals() 方法, 使其按照属性值
比较
LinkedHashSet集合
LinkedHashSet特点:
1. 元素存取有序 (存入和取出顺序一致)
2. 元素不可重复
3. 没有索引
LinkedHashSet底层数据结构:
哈希表 + 链表 (也就是: 数组 + 链表或红黑树 + 链表)
其中, 哈希表用于存储数据, 额外的链表用于记录元素添加时的先后顺序, 以便在获取元素时保持顺序一致
总结: 什么时候用List, 什么时候用Set?
要存储的元素可以重复的, 用List集合:
增删少, 用ArrayList
增删多, 用LinkedList
要存储的数据要求不重复, 或者相对一个集合去重, 用Set集合:
不要求存取顺序一致, 用HashSet
要求存取顺序一致, 用LinkedHashSet
可变参数
可变参数:
JDK 5 出现. 指同一个类型的参数, "个数可变"
可变参数的本质就是一个"数组"
格式: 用在方法的参数中
修饰符 返回值类型 方法名(int... 变量名) {
// 可以直接将 变量名 当作 数组名 使用
}
方法名();
注意事项:
1. 可变参数可以传递的参数个数, 可以是 0个, 1个, 多个
2. 一个方法的参数列表中, 只能有一个可变参数
3. 如果方法的参数有多个, 可变参数必须写在参数列表的最后
Collections集合工具类addAll(), shuffle()
java.util.Collections类: 操作集合的工具类
// 静态方法
static <T> boolean addAll(Collection<? super T> c, T... elements):往集合中添加一些元素
static void shuffle(List<?> list): 打乱集合顺序
static <T> void sort(List<T> list): 将集合中元素按照默认规则排序
static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序
Collections集合工具类: sort(List list)
sort(List<T> list): 默认按照"升序"将元素排序
数字, 字母, 都可以按照升序排序
自定义JavaBean对象默认不能排序, 因为不知道如何比较哪个对象大, 哪个对象小
自定义JavaBean对象要想排序, 需要实现 Comparable<E> 接口, 重写 int compareTo(E e) 方法
规则:
this-参数: 升序(从小到大)
参数-this: 降序(从大到小)
Collections集合工具类: sort(List list,Comparator<? super T> )
Comparable接口和Comparator接口区别
Comparable: 让JavaBean自身具有可比较性 (自己和其他人比)
Comparator: 定义一个比较器类, 用比较器对象比 (让第三个人来帮两个人比较)
Comparator使用方式:
1. 定义类实现Comparator<E>接口, 重写 int compare(E o1, E o2) 方法, 泛型为比较元素的类型
规则:
o1-o2: 升序(从小到大)
o2-o1: 降序(从大到小)
2. 在Collections.sort(List<T> list,Comparator<? super T> c)方法中传入自定义比较器对象
今日API
java.util.List<E>接口:
// 常用特有成员方法 (都是按照索引来操作的)
public void add(int index, E element): 将指定的元素, 添加到该集合中的指定位置上
public E get(int index): 返回集合中指定位置的元素
public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素
public E set(int index, E element): 用指定元素替换集合中指定位置的元素, 返回值的更新前的元素
java.util.LinkedList<E>类: 链表结构, 查询慢, 增删快
// 特有成员方法(主要操作开头和末尾元素)
void addFirst(E e): 将指定元素插入此列表的开头
void addLast(E e): 将指定元素添加到此列表的结尾
E getFirst(): 返回此列表的第一个元素
E getLast(): 返回此列表的最后一个元素
E removeFirst(): 移除并返回此列表的第一个元素
E removeLast(): 移除并返回此列表的最后一个元素
E pop(): (其实就是removeFirst())从此列表所表示的堆栈处弹出一个元素 栈stack
void push(E e): (其实就是addFirst())将元素推入此列表所表示的堆栈
java.util.Set<E>接口:
// 成员方法
boolean add(E e): 添加元素. 元素不重复则添加成功, 返回true; 否则返回false
java.util.Collections类: 操作集合的工具类
// 静态方法
static <T> boolean addAll(Collection<T> c, T... elements):往集合中添加一些元素
static void shuffle(List<?> list) :打乱集合顺序
static <T> void sort(List<T> list) :将集合中元素按照默认规则排序
static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序
day02 Collection 泛型笔记 今日内容介绍
Collection单列集合
共性方法
迭代器
增强for
泛型
斗地主发牌练习
Collection
集合概述知识点:
什么是集合
集合和数组有哪些区别
总结:
集合: 长度可变容器, 可以存储多个对象集合和数组的区别:
1. 数组长度不可变; 集合长度可变
2. 数组可以存基本类型或引用类型, 只能存同一种类型; 集合只能存储引用类型元素, 可以是多种类型元素
补充: 集合框架介绍知识点:
集合的整个框架大致如何分类
如何学习集合框架
总结:
Collection接口: 单列集合顶层
|_ List接口: 元素存取有序, 可重复, 有索引
|_ Set接口: 不可重复, 无索引ArrayList<String> list
list.add("b");
list.add("a");
list.add("c");
list.add("c");
{"b", "a", "c", "c"}
HashSet<String> set
set.add("b");
set.add("a");
set.add("c");
set.add("c");
{"a", "b", "c"}学习方法:
学习顶层: 顶层接口/抽象类中共性的方法, 所有子类都可以使用
使用底层: 使用底层子类/实现类创建对象
ArrayList<Integer> list = new ArrayList<>();
Collection<Integer> col = new ArrayList<>(); // 多态, 只在学习时用这种方式创建对象
// 为了调用接口中共性的方法, 避免让你调用子类特有方法
![](./img/01_集合框架介绍.bmp)![](./img/单列集合体系.bmp)补充: Collection常用功能知识点:
Collection接口中定义了哪些方法, 分别有什么作用// 多态创建对象: 父类引用指向子类对象
ArrayList list = new ArrayList();
list.共有方法();
list.特有方法();Collection<String> coll = new ArrayList<>();
// 多态调用方法
coll.共有方法();
总结:
java.util.Collection<E>接口:
// 成员方法(子类都会实现)
boolean add(E e): 把给定的对象添加到当前集合中
void clear(): 清空集合中所有的元素
boolean remove(E e): 把给定的对象在当前集合中删除
boolean contains(E e): 判断当前集合中是否包含给定的对象
boolean isEmpty(): 判断当前集合是否为空(没有元素)
int size(): 返回集合中元素的个数
Object[] toArray(): 把集合中的元素,存储到数组中
Iterator<E> iterator(): 获取集合的迭代器对象 (后面讲到)
Iterator接口: 迭代器
测试Collection接口的方式:
使用多态方式创建对象: Collection c = new ArrayList();
编译看左边, 这样只能调用Collection接口中定义的方法, 不会出现子类特有方法
补充: 5分钟练习: 测试Collection接口的方法
需求:
通过多态方式创建Collection对象, 实现类是ArrayList
在集合中存储一些字符串
测试以下Collection接口中的方法:
boolean add(E e): 把给定的对象添加到当前集合中
void clear(): 清空集合中所有的元素
boolean remove(E e): 把给定的对象在当前集合中删除
boolean contains(E e): 判断当前集合中是否包含给定的对象
boolean isEmpty(): 判断当前集合是否为空(没有元素)
int size(): 返回集合中元素的个数
Object[] toArray(): 把集合中的元素,存储到数组中
代码:
public class Test {
public static void main(String[] args) {
// 使用多态创建Collection对象, 帮助我们区分哪些方法是Collection接口中的共性方法
Collection<String> coll = new ArrayList<>(); // boolean add(E e): 把给定的对象添加到当前集合中
coll.add("张三");
coll.add("李四");
coll.add("王五");
coll.add("赵六");
System.out.println(coll); // void clear(): 清空集合中所有的元素
// coll.clear();
System.out.println(coll); // boolean remove(E e): 把给定的对象在当前集合中删除
boolean b = coll.remove("李四");
System.out.println("remove删除李四的返回值:" + b);
System.out.println(coll); // boolean contains(E e): 判断当前集合中是否包含给定的对象
boolean b2 = coll.contains("王五");
System.out.println("包含王五的返回值:" + b2); // boolean isEmpty(): 判断当前集合是否为空(没有元素)
boolean isEmpty = coll.isEmpty();
System.out.println("集合isEmpty的结果:" + isEmpty); // int size(): 返回集合中元素的个数
int size = coll.size();
System.out.println("集合的元素个数:" + size); // Object[] toArray(): 把集合中的元素,存储到数组中
// 这个方法相当于间接地从集合中取出元素
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr);
}
}
}
迭代器
迭代器Iterator接口介绍和迭代步骤知识点:
什么是迭代
什么是迭代器
如何获取一个集合的迭代器对象
使用迭代器对象遍历出集合中的元素有哪3步
总结:
迭代:
类似于遍历, 判断是否有下一个元素, 有则取出下一个, 直到没有
迭代器:
用于遍历集合的对象java.util.Collection<E>接口:
// 成员方法(子类都会实现)
Iterator<E> iterator(): 获取集合的迭代器对象
java.util.Iterator<E>接口: 迭代器
// 成员方法
boolean hasNext(): 判断是否有下一个元素
E next(): 获取下一个元素
void remove(): 删除next指向的元素
使用迭代器遍历集合的3步:
1. 使用集合对象的 iterator() 获取迭代器对象, 用 Iterator 接口接收.(多态)
2. 使用 Iterator 接口中的 hasNext() 方法, 判断是否有下一个元素
3. 使用 Iterator 接口中的 next() 方法, 获取下一个元素
补充: 使用迭代器的代码实现知识点:
如何按照迭代器的步骤编写代码遍历集合
总结:
// 使用迭代器遍历集合的标准写法:
Iterator<元素类型> iterator = 集合对象.iterator();
while (iterator.hasNext()) {
元素类型 变量名 = iterator.next();
}
补充:
注意事项:
迭代器对象迭代完毕后, 指针已经指向最后一个元素, 没有下一个元素了. 如果想再次从头遍历集合, 要获取新的迭代器对象
5分钟练习: 使用迭代器遍历集合
需求:
通过多态方式创建Collection对象, 实现类是ArrayList
向集合中添加以下人名:
"姚明", "科比", "麦迪", "詹姆斯", "艾弗森"
获取集合的迭代器对象, 并遍历出元素, 打印出来
代码:
public class Test {
public static void main(String[] args) {
// 创建集合对象
Collection<String> coll = new ArrayList<>(); // 添加元素
coll.add("姚明");
coll.add("科比");
coll.add("麦迪");
coll.add("詹姆斯");
coll.add("艾弗森"); /*
使用迭代器进行遍历
*/
// 先通过 "集合对象" 获取迭代器对象
Iterator<String> iterator = coll.iterator();
// 循环判断迭代器中有元素, 则获取元素
while (iterator.hasNext()) {
// 通过next()方法获取元素
String s = iterator.next();
System.out.println(s);
}
}
}
迭代器的实现原理知识点:
集合和迭代器有什么关系
迭代器的 hasNext(), next() 为什么要获取"下一个"元素?
总结:
集合和迭代器对象的关系:
每个集合都有对应的一个迭代器对象
迭代器的原理:
迭代器有一个指针(其实就是个变量, 保存索引值), 最初指向集合的 0 位置
hasNext() 方法可以判断当前索引是否有元素
next() 方法返回当前元素, 并移动指针到下一个索引
![](./img/02_迭代器的实现原理.bmp)补充: 迭代器的并发修改异常知识点:
使用迭代器进行迭代的过程中, 能否执行修改集合的长度的操作?
如何避免出现 ConcurrentModificationException 并发修改异常?
总结:
并发修改异常的原因:
在使用迭代器迭代的过程中, 如果执行了改变集合长度的操作 (如 add(), remove(), clear()), 则会抛出 ConcurrentModificationException 并发修改异常. 如何避免:
1. 迭代过程中不要修改集合长度
2. 在迭代的过程中添加, 删除元素, 要使用迭代器自带的方法, 而不能使用集合的方法
Iterator中的 remove() 方法
ListIterator中的 add(), remove() 方法
补充: 增强for循环知识点:
增强for循环有什么作用
增强for循环底层使用什么实现
增强for循环的格式是什么
总结:
增强for:
也称foreach循环, JDK 5出现, 用于遍历集合, 底层采用迭代器作用:
遍历数组
遍历集合增强for格式: for(元素的数据类型 变量名 : Collection集合或数组名){
//操作代码
} // 数组示例
int[] arr = {1,2,3};
for (int i : arr) {
System.out.println(i);
} // 集合示例
Collection<Integer> c = new ArrayList<>();
for (int i : c) {
System.out.println(i);
}增强for对于数组和集合的操作:
对数组只是写法上的优化, 底层还是普通for循环
对集合是通过迭代器实现的
补充:
增强for, 普通for, 迭代器的区别:
增强for:
优点: 获取元素很方便, 格式简单
缺点: 没有普通for中的索引, 没有迭代器对象可以进行元素的增删
应用场景: 适用于遍历获取数组和集合元素的场景
普通for:
优点: 有索引可以使用, 某些方式可以在遍历过程中增删元素
缺点: 格式繁琐
应用场景: 需要用到索引的场景
迭代器:
优点: 可以使用迭代器对象的方法操作元素
缺点: 格式繁琐
应用场景: 需要在迭代过程中增删元素的场景
5分钟练习: 增强for遍历集合
需求:
通过多态方式创建Collection对象, 实现类是ArrayList
向集合中添加以下人名:
"姚明", "科比", "麦迪", "詹姆斯", "艾弗森"
用增强for遍历出元素, 打印出来
代码:
public class Test {
public static void main(String[] args) {
// 创建集合
Collection<String> coll = new ArrayList<>(); // 添加元素
coll.add("姚明");
coll.add("科比");
coll.add("麦迪");
coll.add("詹姆斯");
coll.add("艾弗森"); // 使用增强for循环遍历: 快捷键: 集合/数组名.for
for (String s : coll) {
System.out.println(s);
}
}
}
泛型
泛型的概述以及不使用泛型产生的问题知识点:
什么是泛型
总结:
泛型: Generic Type. JDK 5 增加. 是一种未知的数据类型
定义集合时, 某些方法不知道使用什么类型时, 就可以使用泛型
创建集合对象时, 需要确定泛型具体的类型
泛型可以看作是一个"变量", 用来接收数据类型int a = 10;
a = 20;定义:
<E>: 声明了一个泛型 (声明了一个变量)
E: 使用E这种类型作为数据类型 (参数数据类型, 返回值类型)
使用:
ArrayList<Integer>: 确定了泛型的具体类型public class ArrayList<E> { // E可以表示任意的引用类型, 但是只能表示某一种
String name;
int age;
E e;
public boolean add(E e){} String
public E get(int index){}
}ArrayList<String> list = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<>();
// 如果没有泛型, 想设计添加指定类型的集合, 就不好实现:
// 添加String元素的集合
public class ArrayList1 {
public boolean add(String e){}
public String get(int index){}
}
// 添加Student元素的集合
public class ArrayList2 {
public boolean add(Student e){}
public Student get(int index){}
}
// 即便可以使用Object作为参数, 但这样任意类型元素都能添加, 却无法限制只能添加某一种特定类型的元素
public class ArrayList {
public boolean add(Object e){}
public Object get(int index){}
}
// 遍历的时候强转类型就可能出问题
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
String s = (String)o; // 万一取出的元素不是String呢?
}
补充: 泛型的好处知识点:
如果集合不使用泛型, 存储的是什么类型的元素
集合不使用泛型会有什么安全隐患
总结:
不使用泛型的问题:
集合实际存储的是 Object 类型, 存入的元素无论是什么类型, 都会被提升为 Object, 取出来的也是 Object, 要想调用元素特有方法, 就要向下转型, 有可能发生类型转换异常 ClassCastException泛型的好处:
1. 避免了类型转换的麻烦
2. 将运行时的类型转换异常, 转移到了编译时期 (有利于程序员提前发现问题)
补充: 定义和使用含泛型的类知识点:
如何在类的声明上定义泛型
如何在类中使用泛型
何时确定类中泛型的具体类型
总结:
定义泛型
<泛型名> 泛型的"定义"和"使用"
泛型在"定义"时, "不能是"具体的类型, 只是一个变量名: public class ArrayList<E> {}
泛型在"使用"时, "必须是"具体的数据类型 ArrayList<Integer>
// 带有泛型的类定义格式
修饰符 class 类名<代表泛型的名字> { // 泛型的变量一般用一个大写字母表示, 但也可以是多个字母 }
类中的泛型, "在创建对象时", 确定泛型的具体类型
补充: 5分钟练习: 定义带有泛型的类
需求:
定义类: GenericClass, 带有泛型 <E>
私有成员变量: E name;
提供 set/get方法
定义测试类
创建GenericClass对象, 将泛型替换为Integer, 调用setName()设置值并调用getName()获取值并打印
创建GenericClass对象, 将泛型替换为String, 调用setName()设置值并调用getName()获取值并打印
代码:
public class GenericClass<E> { // 在类名后面声明泛型, 定义在类上的泛型, 整个类中都可以使用
private E name; // 泛型声明后, 可以用于成员变量的类型 public E getName() { // 泛型声明后, 可以用于方法返回值的类型
return name;
} public void setName(E name) { // 泛型声明后, 可以用于方法参数的类型
this.name = name;
}
}public class Test {
public static void main(String[] args) {
// 带有泛型的类, 泛型要在"创建该类对象"时, 确定具体的类型
GenericClass<Integer> gc1 = new GenericClass<>();
// // 因为泛型确定为了Integer, 所以方法的参数也只能是Integer
gc1.setName(1);
// 因为泛型确定为了Integer, 所以方法的返回值也只能是Integer
Integer name = gc1.getName();
System.out.println(name);
GenericClass<String> gc2 = new GenericClass<>();
gc2.setName("你好");
String name1 = gc2.getName();
System.out.println(name1); // 泛型的好处, 只需要定义一个类, 就可以实现:
// 让该类只能使用"某一种"具体的类型
}
}
定义含有泛型的方法与使用知识点:
如何在方法中定义泛型
方法中的泛型何时确定具体类型
总结:
方法中的泛型定义位置:
修饰符 和 返回值类型 之间
// 带有泛型的方法定义格式
修饰符 <代表泛型的名字> 返回值类型 方法名(参数){ }
方法中定义泛型后, 返回值类型和参数类型都可以使用泛型方法泛型的使用:
"在调用方法时"确定泛型的具体类型
补充: 5分钟练习: 在方法中定义泛型并使用
需求:
定义类: GenericMethod
定义成员方法:
public <M> void method(M m) {
System.out.println(m);
}
定义静态方法:
public static <S> void staticMethod(S s) {
System.out.println(s);
}
定义测试类:
创建GenericMethod对象
调用method()方法, 传递10
调用method()方法, 传递"abc"
调用staticMethod()方法, 传递"静态方法"
代码:
public class GenericMethod { // 定义成员方法:
public <M> void method(M m) { // 方法中定义的泛型, 只在方法内部有效
System.out.println(m);
} // 定义静态方法:
public static <S> void staticMethod(S s) {
System.out.println(s);
}
}public class Test {
public static void main(String[] args) {
// 创建对象调用方法
GenericMethod gm = new GenericMethod(); // 调用成员方法
// 方法的泛型, 在调用方法传递参数时, 确定具体类型
gm.method(1); // 确定为Integer
gm.method("a"); // 确定为String
// 调用静态方法
// 也是在调用方法传递参数时, 确定具体类型
GenericMethod.staticMethod(1);
GenericMethod.staticMethod("a");
}
}
定义与使用含有泛型的接口知识点:
如何在接口中定义泛型
接口中的泛型何时确定具体类型
总结:
定义泛型接口与定义泛型类一样 // 带有泛型的类定义格式
修饰符 interface 接口名<代表泛型的变量> { }
public interface GenericInterface<I> {
void method(I i);
}实现类实现了泛型接口后可以有2种选择:
1. "定义实现类时", 确定泛型的具体类型
public class A implements GenericInterface<String> {
@Override
public void method(String i) {} // 所有使用泛型的地方都会确定具体类型
}
2. 定义实现类时仍然沿用泛型, 直到"创建该实现类对象时"才确定泛型的具体类型
public class A<I> implements GenericInterface<I> {
@Override
public void method(I i) {} // 泛型不需要确定具体类型, 当创建该类对象时再确定
}
A<Integer> a = new A<>();
a.method(1);
A<String> a2 = new A<>();
a2.method("dsfdsaf");
补充:
/*
定义含有泛型的接口
*/
public interface GenericInterface<I> {
public abstract void method(I i);
}/*
含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型
public interface Iterator<E> {
E next();
}
Scanner类实现了Iterator接口,并指定接口的泛型为String,所以重写的next方法泛型默认就是String
public final class Scanner implements Iterator<String>{
public String next() {}
}
*/
public class GenericInterfaceImpl1 implements GenericInterface<String>{
@Override
public void method(String s) {
System.out.println(s);
}
}/*
含有泛型的接口第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走
就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型
public interface List<E>{
boolean add(E e);
E get(int index);
}
public class ArrayList<E> implements List<E>{
public boolean add(E e) {}
public E get(int index) {}
}
*/
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
@Override
public void method(I i) {
System.out.println(i);
}
}/*
测试含有泛型的接口
*/
public class Demo04GenericInterface {
public static void main(String[] args) {
//创建GenericInterfaceImpl1对象
GenericInterfaceImpl1 gi1 = new GenericInterfaceImpl1();
gi1.method("字符串"); //创建GenericInterfaceImpl2对象
GenericInterfaceImpl2<Integer> gi2 = new GenericInterfaceImpl2<>();
gi2.method(10); GenericInterfaceImpl2<Double> gi3 = new GenericInterfaceImpl2<>();
gi3.method(8.8);
}
}泛型定义总结:
定义在类上的泛型:
有效范围: 整个类中都有效
何时确定具体类型: 创建该类对象时确定泛型的具体类型
定义在方法上的泛型:
有效范围: 方法中(包括返回值类型和参数类型)有效
何时确定具体类型: 调用方法传参时确定泛型的具体类型
定义在接口上的泛型:
有效范围: 接口中
何时确定具体类型:
1. 子接口或实现类定义时确定泛型的具体类型
2. 创建实现类对象时确定泛型的具体类型
泛型通配符知识点:
什么是泛型通配符? 泛型通配符如何使用?
什么是泛型的上限
什么是泛型的下限public void method(ArrayList<? extends Cat> list) {
// ArrayList<?> list = new ArrayList<String>();
// ...
}
ArrayList<Animal> list = new ...;
ArrayList<Cat> list2 = new ...;
ArrayList<String> list3 = new ...;
method(list);
method(list2);
总结:
泛型通配符:
不知道使用什么类型来接收时, 此时可以使用 <?> 来表示未知通配符
示例: List<?> list 接收泛型是任意类型的List对象
public void addAll(List<?> list){
// ...
} ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
addAll(list1);
addAll(list2);
注意: 只能接收数据, 不能调用方法存储元素
List<?> list 这种集合不能调用 add() 添加元素, 只能调用 get() 获取元素
List<?> list 其实是一个变量, 所以可以将一个集合赋值给它
如: List<?> list = new ArrayList<String>(); // 正确
但是不能直接new 泛型为?的集合对象:
如: List<?> list = new ArrayList<?>(); // 错误
使用方式:
不能创建对象使用
只能作为方法参数使用. (减少方法重载)泛型的上限:
格式: 类型名称<? extends 类名> 对象名称
示例: List<? extends Number> list
作用: 只能接收泛型为该类型及其子类的对象 (Number及其子类的泛型)泛型的下限:
格式: 类型名称<? super 类名> 对象名称
示例: List<? super Number> list
作用: 只能接收泛型为该类型及其父类型的对象 (Number及其父类的泛型)
补充: 斗地主练习
斗地主: 需求分析知识点:
斗地主发牌: 使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌
装牌:
牌盒: 有牌 String
"♣A" ♤ ♡ ♣ ♢ A 2 3 ... 9 10 J Q K
大王 小王
洗牌: 把牌盒集合元素打乱顺序
发牌:
遍历牌盒集合
3个人: 3个集合ArrayList
发牌: 取模%
11 % 3 = 2
10 % 3 = 1
9 % 3 = 0
8 % 3 = 2
7 % 3 = 1
6 % 3 = 0
3张底牌: 底牌集合
看牌: 打印3个人的集合和底牌集合
总结:
Collection接口
java.util.Collections类: 操作集合的工具类
// 静态方法
static void shuffle(List<?> list): 随机打乱集合中元素的顺序
![](./img/04_斗地主案例的需求分析.bmp)补充: 斗地主: 代码实现
10分钟练习: 实现斗地主发牌
1. 准备牌:
花色: String[] colors 存储4种花色: {"♠","♥","♣","♦"}
面值: String[] numbers 存储13个序号: {"2","A","K","Q","J","10","9","8","7","6","5","4","3"}
牌盒: ArrayList<String> poker 存储拼接好的牌
添加大小王
拼接牌:
每种花色+每种面值
双层for循环嵌套
装入牌盒
poker.add(...)
2. 洗牌
Collections.shuffle(poker);
3. 发牌
创建4个ArrayList集合, 分别表示3个玩家和底牌
for循环遍历poker集合
如果 索引 >= 51, 存入底牌集合
如果 索引 % 3 == 0, 存入玩家1集合
如果 索引 % 3 == 1, 存入玩家2集合
如果 索引 % 3 == 2, 存入玩家3集合
4. 看牌
打印3个玩家集合和底牌集合
代码:
public class Test {
public static void main(String[] args) {
/*
准备牌
*/
// 创建牌盒集合
ArrayList<String> poker = new ArrayList<>(); // 定义2个数组, 分别存储花色和数值
String[] colors = {"♤", "♡", "♣", "♢"};
String[] numbers = {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"}; // 添加大小王
poker.add("大王");
poker.add("小王");
// 嵌套循环生成牌, 并装牌
for (String color : colors) {
for (String number : numbers) {
// 添加到牌盒
poker.add(color + number);
}
} /*
洗牌
*/
Collections.shuffle(poker); /*
发牌
*/
// 定义玩家集合和底牌集合
ArrayList<String> player01 = new ArrayList<>();
ArrayList<String> player02 = new ArrayList<>();
ArrayList<String> player03 = new ArrayList<>();
ArrayList<String> diPai = new ArrayList<>();
// 轮流发牌
for (int i = 0; i < poker.size(); i++) {
// 获取一张牌
String pai = poker.get(i);
// 判断
if (i >= 51) {
// 给底牌
diPai.add(pai);
} else if (i % 3 == 0) {
// 给玩家1
player01.add(pai);
} else if (i % 3 == 1) {
// 给玩家2
player02.add(pai);
} else if (i % 3 == 2) {
// 给玩家3
player03.add(pai);
}
} /*
看牌
*/
System.out.println("刘备:" + player01);
System.out.println("关羽:" + player02);
System.out.println("张飞:" + player03);
System.out.println("底牌:" + diPai);
}
} 今日API
java.util.Collection<E>接口:
// 成员方法(子类都会实现)
boolean add(E e): 把给定的对象添加到当前集合中
void clear(): 清空集合中所有的元素
boolean remove(E e): 把给定的对象在当前集合中删除
boolean contains(E e): 判断当前集合中是否包含给定的对象
boolean isEmpty(): 判断当前集合是否为空(没有元素)
int size(): 返回集合中元素的个数
Object[] toArray(): 把集合中的元素,存储到数组中
Iterator<E> iterator(): 获取集合的迭代器对象java.util.Iterator<E>接口: 迭代器
// 成员方法
boolean hasNext(): 判断是否有下一个元素
E next(): 获取下一个元素
void remove(): 删除调用完next()指向的元素
java.util.Collections工具类
// 静态方法
static void shuffle(List<?> list): 随机打乱集合中元素的顺序
今日目标 能够说出集合与数组的区别
1. 集合长度可变, 数组长度一旦创建不可改变
2. 集合能存多种引用类型, 数组可以存一种基本和引用类型
说出Collection集合的常用功能
添加: boolean add(E e)
删除: boolean remove(E e)
清空: void clear()
长度: int size()
判断是否包含: boolean contains(E e)
判断集合是否为空: boolean isEmpty()
转数组: Object[] toArray()
获取迭代器对象: Iterator<E> iterator()
能够使用迭代器对集合进行取元素
Iterator<元素类型> it = 集合对象.iterator();
while (it.hasNext()) {
元素类型 变量名 = it.next();
}
能够使用foreach循环遍历集合
for (元素类型 变量名 : 集合名) {
// 使用变量
}for (元素类型 变量名 : 数组名) {
// 使用变量
}集合.for
能够使用泛型定义集合对象
ArrayList<String> list = new ArrayList<>();好处:
1. 避免了类型转换的麻烦
2. 将运行时可能产生的类型转换异常, 提前到了编译时期
能够理解泛型上下限
上限: <? extends E> 可以接收泛型为E类型或E类型的子类类型
下限: <? super E> 可以接收泛型为E类型或E类型的父类类型
能够阐述泛型通配符的作用
<?>
<? extends E>
<? super E>
扩展泛型的限制范围
|
|